diff --git a/.reuse/dep5 b/.reuse/dep5 index 73814634..e051f49f 100644 --- a/.reuse/dep5 +++ b/.reuse/dep5 @@ -3,7 +3,7 @@ Upstream-Name: libopenshot Upstream-Contact: Jonathan Thomas Source: https://github.com/OpenShot/libopenshot -Files: examples/*.png examples/*.svg examples/*.wav examples/*.mp4 examples/*.avi doc/images/* +Files: examples/*.png examples/*.svg examples/*.wav examples/*.mp4 examples/* doc/images/* Copyright: OpenShot Studios, LLC License: LGPL-3.0-or-later diff --git a/examples/Example.cpp b/examples/Example.cpp index eaaa3abc..4c7b96c2 100644 --- a/examples/Example.cpp +++ b/examples/Example.cpp @@ -13,25 +13,56 @@ #include #include #include +#include #include "Clip.h" #include "Frame.h" #include "FFmpegReader.h" #include "Timeline.h" +#include "Profiles.h" using namespace openshot; int main(int argc, char* argv[]) { - - // FFmpeg Reader performance test - FFmpegReader r9("/home/jonathan/Downloads/pts-test-files/broken-files/lady-talking-1.mp4"); - r9.Open(); - for (long int frame = 1; frame <= r9.info.video_length; frame++) - { - std::cout << "Requesting Frame: #: " << frame << std::endl; - std::shared_ptr f = r9.GetFrame(frame); + QString filename = "/home/jonathan/test-crash.osp"; + //QString filename = "/home/jonathan/Downloads/drive-download-20221123T185423Z-001/project-3363/project-3363.osp"; + //QString filename = "/home/jonathan/Downloads/drive-download-20221123T185423Z-001/project-3372/project-3372.osp"; + //QString filename = "/home/jonathan/Downloads/drive-download-20221123T185423Z-001/project-3512/project-3512.osp"; + QString project_json = ""; + QFile file(filename); + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { + std::cout << "File error!" << std::endl; + exit(1); + } else { + while (!file.atEnd()) { + QByteArray line = file.readLine(); + project_json += line; + } } - r9.Close(); + + // Open timeline reader + std::cout << "Project JSON length: " << project_json.length() << std::endl; + Timeline r(1280, 720, openshot::Fraction(30, 1), 44100, 2, openshot::LAYOUT_STEREO); + r.SetJson(project_json.toStdString()); + r.DisplayInfo(); + r.Open(); + + // Get max frame + int64_t max_frame = r.GetMaxFrame(); + std::cout << "max_frame: " << max_frame << ", r.info.video_length: " << r.info.video_length << std::endl; + + for (long int frame = 1; frame <= max_frame; frame++) + { + float percent = (float(frame) / max_frame) * 100.0; + std::cout << "Requesting Frame #: " << frame << " (" << percent << "%)" << std::endl; + std::shared_ptr f = r.GetFrame(frame); + + // Preview frame image + if (frame % 1 == 0) { + f->Save("preview.jpg", 1.0, "jpg", 100); + } + } + r.Close(); exit(0); } diff --git a/examples/example_profile1 b/examples/example_profile1 new file mode 100644 index 00000000..136c899d --- /dev/null +++ b/examples/example_profile1 @@ -0,0 +1,11 @@ +description=HD 720p 24 fps +frame_rate_num=24 +frame_rate_den=1 +width=1280 +height=720 +progressive=1 +sample_aspect_num=1 +sample_aspect_den=1 +display_aspect_num=16 +display_aspect_den=9 +colorspace=709 diff --git a/examples/example_profile2 b/examples/example_profile2 new file mode 100644 index 00000000..7178d410 --- /dev/null +++ b/examples/example_profile2 @@ -0,0 +1,11 @@ +description=HD 1080i 29.97 fps +frame_rate_num=30000 +frame_rate_den=1001 +width=1920 +height=1080 +progressive=0 +sample_aspect_num=1 +sample_aspect_den=1 +display_aspect_num=16 +display_aspect_den=9 +colorspace=709 diff --git a/src/FFmpegWriter.cpp b/src/FFmpegWriter.cpp index cc6f1217..8c147a10 100644 --- a/src/FFmpegWriter.cpp +++ b/src/FFmpegWriter.cpp @@ -52,10 +52,10 @@ static int set_hwframe_ctx(AVCodecContext *ctx, AVBufferRef *hw_device_ctx, int6 return -1; } frames_ctx = (AVHWFramesContext *)(hw_frames_ref->data); - frames_ctx->format = hw_en_av_pix_fmt; + frames_ctx->format = hw_en_av_pix_fmt; frames_ctx->sw_format = AV_PIX_FMT_NV12; - frames_ctx->width = width; - frames_ctx->height = height; + frames_ctx->width = width; + frames_ctx->height = height; frames_ctx->initial_pool_size = 20; if ((err = av_hwframe_ctx_init(hw_frames_ref)) < 0) { std::clog << "Failed to initialize HW frame context. " << @@ -75,7 +75,7 @@ static int set_hwframe_ctx(AVCodecContext *ctx, AVBufferRef *hw_device_ctx, int6 FFmpegWriter::FFmpegWriter(const std::string& path) : path(path), oc(NULL), audio_st(NULL), video_st(NULL), samples(NULL), audio_outbuf(NULL), audio_outbuf_size(0), audio_input_frame_size(0), audio_input_position(0), - initial_audio_input_frame_size(0), img_convert_ctx(NULL), cache_size(8), num_of_rescalers(32), + initial_audio_input_frame_size(0), img_convert_ctx(NULL), cache_size(1), num_of_rescalers(1), rescaler_position(0), video_codec_ctx(NULL), audio_codec_ctx(NULL), is_writing(false), video_timestamp(0), audio_timestamp(0), original_sample_rate(0), original_channels(0), avr(NULL), avr_planar(NULL), is_open(false), prepare_streams(false), write_header(false), write_trailer(false), audio_encoder_buffer_size(0), audio_encoder_buffer(NULL) { @@ -244,9 +244,9 @@ void FFmpegWriter::SetVideoOptions(bool has_video, std::string codec, Fraction f info.pixel_ratio.num = pixel_ratio.num; info.pixel_ratio.den = pixel_ratio.den; } - if (bit_rate >= 1000) // bit_rate is the bitrate in b/s + if (bit_rate >= 1000) // bit_rate is the bitrate in b/s info.video_bit_rate = bit_rate; - if ((bit_rate >= 0) && (bit_rate < 256)) // bit_rate is the bitrate in crf + if ((bit_rate >= 0) && (bit_rate < 256)) // bit_rate is the bitrate in crf info.video_bit_rate = bit_rate; info.interlaced_frame = interlaced; @@ -435,7 +435,7 @@ void FFmpegWriter::SetOption(StreamType stream, std::string name, std::string va av_opt_set_int(c->priv_data, "qp", std::max(std::min(std::stoi(value), 63), 4), 0); // 4-63 break; case AV_CODEC_ID_VP9 : - c->bit_rate = 0; // Must be zero! + c->bit_rate = 0; // Must be zero! av_opt_set_int(c->priv_data, "qp", std::min(std::stoi(value), 63), 0); // 0-63 if (std::stoi(value) == 0) { av_opt_set(c->priv_data, "preset", "veryslow", 0); @@ -495,7 +495,7 @@ void FFmpegWriter::SetOption(StreamType stream, std::string name, std::string va av_opt_set_int(c->priv_data, "crf", std::max(std::min(std::stoi(value), 63), 4), 0); // 4-63 break; case AV_CODEC_ID_VP9 : - c->bit_rate = 0; // Must be zero! + c->bit_rate = 0; // Must be zero! av_opt_set_int(c->priv_data, "crf", std::min(std::stoi(value), 63), 0); // 0-63 if (std::stoi(value) == 0) { av_opt_set(c->priv_data, "preset", "veryslow", 0); @@ -542,7 +542,7 @@ void FFmpegWriter::SetOption(StreamType stream, std::string name, std::string va // This might be better in an extra methods as more options // and way to set quality are possible #if (LIBAVCODEC_VERSION_MAJOR >= 58) - // FFmpeg 4.0+ + // FFmpeg 4.0+ switch (c->codec_id) { case AV_CODEC_ID_AV1 : c->bit_rate = 0; @@ -726,79 +726,79 @@ void FFmpegWriter::write_queued_frames() { // Create blank exception bool has_error_encoding_video = false; - // Process all audio frames (in a separate thread) - if (info.has_audio && audio_st && !queued_audio_frames.empty()) - write_audio_packets(false); + // Process all audio frames (in a separate thread) + if (info.has_audio && audio_st && !queued_audio_frames.empty()) + write_audio_packets(false); - // Loop through each queued image frame - while (!queued_video_frames.empty()) { - // Get front frame (from the queue) - std::shared_ptr frame = queued_video_frames.front(); + // Loop through each queued image frame + while (!queued_video_frames.empty()) { + // Get front frame (from the queue) + std::shared_ptr frame = queued_video_frames.front(); - // Add to processed queue - processed_frames.push_back(frame); + // Add to processed queue + processed_frames.push_back(frame); - // Encode and add the frame to the output file - if (info.has_video && video_st) - process_video_packet(frame); + // Encode and add the frame to the output file + if (info.has_video && video_st) + process_video_packet(frame); - // Remove front item - queued_video_frames.pop_front(); + // Remove front item + queued_video_frames.pop_front(); - } // end while + } // end while - // Loop back through the frames (in order), and write them to the video file - while (!processed_frames.empty()) { - // Get front frame (from the queue) - std::shared_ptr frame = processed_frames.front(); + // Loop back through the frames (in order), and write them to the video file + while (!processed_frames.empty()) { + // Get front frame (from the queue) + std::shared_ptr frame = processed_frames.front(); - if (info.has_video && video_st) { - // Add to deallocate queue (so we can remove the AVFrames when we are done) - deallocate_frames.push_back(frame); + if (info.has_video && video_st) { + // Add to deallocate queue (so we can remove the AVFrames when we are done) + deallocate_frames.push_back(frame); - // Does this frame's AVFrame still exist - if (av_frames.count(frame)) { - // Get AVFrame - AVFrame *frame_final = av_frames[frame]; + // Does this frame's AVFrame still exist + if (av_frames.count(frame)) { + // Get AVFrame + AVFrame *frame_final = av_frames[frame]; - // Write frame to video file - bool success = write_video_packet(frame, frame_final); - if (!success) - has_error_encoding_video = true; - } - } + // Write frame to video file + bool success = write_video_packet(frame, frame_final); + if (!success) + has_error_encoding_video = true; + } + } - // Remove front item - processed_frames.pop_front(); - } + // Remove front item + processed_frames.pop_front(); + } - // Loop through, and deallocate AVFrames - while (!deallocate_frames.empty()) { - // Get front frame (from the queue) - std::shared_ptr frame = deallocate_frames.front(); + // Loop through, and deallocate AVFrames + while (!deallocate_frames.empty()) { + // Get front frame (from the queue) + std::shared_ptr frame = deallocate_frames.front(); - // Does this frame's AVFrame still exist - if (av_frames.count(frame)) { - // Get AVFrame - AVFrame *av_frame = av_frames[frame]; + // Does this frame's AVFrame still exist + if (av_frames.count(frame)) { + // Get AVFrame + AVFrame *av_frame = av_frames[frame]; - // Deallocate buffer and AVFrame - av_freep(&(av_frame->data[0])); - AV_FREE_FRAME(&av_frame); - av_frames.erase(frame); - } + // Deallocate buffer and AVFrame + av_freep(&(av_frame->data[0])); + AV_FREE_FRAME(&av_frame); + av_frames.erase(frame); + } - // Remove front item - deallocate_frames.pop_front(); - } + // Remove front item + deallocate_frames.pop_front(); + } - // Done writing - is_writing = false; + // Done writing + is_writing = false; - // Raise exception from main thread - if (has_error_encoding_video) - throw ErrorEncodingVideo("Error while writing raw video frame", -1); + // Raise exception from main thread + if (has_error_encoding_video) + throw ErrorEncodingVideo("Error while writing raw video frame", -1); } // Write a block of frames from a reader @@ -876,21 +876,21 @@ void FFmpegWriter::flush_encoders() { int error_code = 0; #if IS_FFMPEG_3_2 - // Encode video packet (latest version of FFmpeg) - error_code = avcodec_send_frame(video_codec_ctx, NULL); - got_packet = 0; - while (error_code >= 0) { - error_code = avcodec_receive_packet(video_codec_ctx, pkt); - if (error_code == AVERROR(EAGAIN)|| error_code == AVERROR_EOF) { - got_packet = 0; - // Write packet - avcodec_flush_buffers(video_codec_ctx); - break; - } - av_packet_rescale_ts(pkt, video_codec_ctx->time_base, video_st->time_base); - pkt->stream_index = video_st->index; - error_code = av_interleaved_write_frame(oc, pkt); - } + // Encode video packet (latest version of FFmpeg) + error_code = avcodec_send_frame(video_codec_ctx, NULL); + got_packet = 0; + while (error_code >= 0) { + error_code = avcodec_receive_packet(video_codec_ctx, pkt); + if (error_code == AVERROR(EAGAIN)|| error_code == AVERROR_EOF) { + got_packet = 0; + // Write packet + avcodec_flush_buffers(video_codec_ctx); + break; + } + av_packet_rescale_ts(pkt, video_codec_ctx->time_base, video_st->time_base); + pkt->stream_index = video_st->index; + error_code = av_interleaved_write_frame(oc, pkt); + } #else // IS_FFMPEG_3_2 // Encode video packet (older than FFmpeg 3.2) @@ -925,62 +925,62 @@ void FFmpegWriter::flush_encoders() { // FLUSH AUDIO ENCODER if (info.has_audio) { - for (;;) { + for (;;) { #if IS_FFMPEG_3_2 - AVPacket* pkt = av_packet_alloc(); + AVPacket* pkt = av_packet_alloc(); #else - AVPacket* pkt; - av_init_packet(pkt); + AVPacket* pkt; + av_init_packet(pkt); #endif - pkt->data = NULL; - pkt->size = 0; - pkt->pts = pkt->dts = audio_timestamp; + pkt->data = NULL; + pkt->size = 0; + pkt->pts = pkt->dts = audio_timestamp; - /* encode the image */ - int error_code = 0; - int got_packet = 0; + /* encode the image */ + int error_code = 0; + int got_packet = 0; #if IS_FFMPEG_3_2 - error_code = avcodec_send_frame(audio_codec_ctx, NULL); + error_code = avcodec_send_frame(audio_codec_ctx, NULL); #else - error_code = avcodec_encode_audio2(audio_codec_ctx, pkt, NULL, &got_packet); + error_code = avcodec_encode_audio2(audio_codec_ctx, pkt, NULL, &got_packet); #endif - if (error_code < 0) { - ZmqLogger::Instance()->AppendDebugMethod( - "FFmpegWriter::flush_encoders ERROR [" - + av_err2string(error_code) + "]", - "error_code", error_code); - } - if (!got_packet) { - break; - } + if (error_code < 0) { + ZmqLogger::Instance()->AppendDebugMethod( + "FFmpegWriter::flush_encoders ERROR [" + + av_err2string(error_code) + "]", + "error_code", error_code); + } + if (!got_packet) { + break; + } - // Since the PTS can change during encoding, set the value again. This seems like a huge hack, - // but it fixes lots of PTS related issues when I do this. - pkt->pts = pkt->dts = audio_timestamp; + // Since the PTS can change during encoding, set the value again. This seems like a huge hack, + // but it fixes lots of PTS related issues when I do this. + pkt->pts = pkt->dts = audio_timestamp; - // Scale the PTS to the audio stream timebase (which is sometimes different than the codec's timebase) - av_packet_rescale_ts(pkt, audio_codec_ctx->time_base, audio_st->time_base); + // Scale the PTS to the audio stream timebase (which is sometimes different than the codec's timebase) + av_packet_rescale_ts(pkt, audio_codec_ctx->time_base, audio_st->time_base); - // set stream - pkt->stream_index = audio_st->index; - pkt->flags |= AV_PKT_FLAG_KEY; + // set stream + pkt->stream_index = audio_st->index; + pkt->flags |= AV_PKT_FLAG_KEY; - // Write packet - error_code = av_interleaved_write_frame(oc, pkt); - if (error_code < 0) { - ZmqLogger::Instance()->AppendDebugMethod( - "FFmpegWriter::flush_encoders ERROR [" - + av_err2string(error_code) + "]", - "error_code", error_code); - } + // Write packet + error_code = av_interleaved_write_frame(oc, pkt); + if (error_code < 0) { + ZmqLogger::Instance()->AppendDebugMethod( + "FFmpegWriter::flush_encoders ERROR [" + + av_err2string(error_code) + "]", + "error_code", error_code); + } - // Increment PTS by duration of packet - audio_timestamp += pkt->duration; + // Increment PTS by duration of packet + audio_timestamp += pkt->duration; - // deallocate memory for packet - AV_FREE_PACKET(pkt); - } - } + // deallocate memory for packet + AV_FREE_PACKET(pkt); + } + } } @@ -995,6 +995,12 @@ void FFmpegWriter::close_video(AVFormatContext *oc, AVStream *st) } } #endif // USE_HW_ACCEL + + // Free any previous memory allocations + if (video_codec_ctx != nullptr) { + AV_FREE_CONTEXT(video_codec_ctx); + av_free(video_codec_ctx); + } } // Close the audio codec @@ -1020,6 +1026,12 @@ void FFmpegWriter::close_audio(AVFormatContext *oc, AVStream *st) SWR_FREE(&avr_planar); avr_planar = NULL; } + + // Free any previous memory allocations + if (audio_codec_ctx != nullptr) { + AV_FREE_CONTEXT(audio_codec_ctx); + av_free(audio_codec_ctx); + } } // Close the writer @@ -1279,7 +1291,7 @@ AVStream *FFmpegWriter::add_video_stream() { } } - //TODO: Implement variable bitrate feature (which actually works). This implementation throws + //TODO: Implement variable bitrate feature (which actually works). This implementation throws //invalid bitrate errors and rc buffer underflow errors, etc... //c->rc_min_rate = info.video_bit_rate; //c->rc_max_rate = info.video_bit_rate; @@ -1437,8 +1449,7 @@ void FFmpegWriter::open_audio(AVFormatContext *oc, AVStream *st) { "FFmpegWriter::open_audio", "audio_codec_ctx->thread_count", audio_codec_ctx->thread_count, "audio_input_frame_size", audio_input_frame_size, - "buffer_size", - AVCODEC_MAX_AUDIO_FRAME_SIZE + MY_INPUT_BUFFER_PADDING_SIZE); + "buffer_size", AVCODEC_MAX_AUDIO_FRAME_SIZE + MY_INPUT_BUFFER_PADDING_SIZE); } // open video codec @@ -1535,7 +1546,7 @@ void FFmpegWriter::open_video(AVFormatContext *oc, AVStream *st) { switch (video_codec_ctx->codec_id) { case AV_CODEC_ID_H264: - video_codec_ctx->max_b_frames = 0; // At least this GPU doesn't support b-frames + video_codec_ctx->max_b_frames = 0; // At least this GPU doesn't support b-frames video_codec_ctx->profile = FF_PROFILE_H264_BASELINE | FF_PROFILE_H264_CONSTRAINED; av_opt_set(video_codec_ctx->priv_data, "preset", "slow", 0); av_opt_set(video_codec_ctx->priv_data, "tune", "zerolatency", 0); @@ -1556,9 +1567,7 @@ void FFmpegWriter::open_video(AVFormatContext *oc, AVStream *st) { // set hw_frames_ctx for encoder's AVCodecContext int err; - if ((err = set_hwframe_ctx( - video_codec_ctx, hw_device_ctx, - info.width, info.height)) < 0) + if ((err = set_hwframe_ctx(video_codec_ctx, hw_device_ctx, info.width, info.height)) < 0) { ZmqLogger::Instance()->AppendDebugMethod( "FFmpegWriter::open_video (set_hwframe_ctx) ERROR faled to set hwframe context", @@ -1590,441 +1599,441 @@ void FFmpegWriter::open_video(AVFormatContext *oc, AVStream *st) { // write all queued frames' audio to the video file void FFmpegWriter::write_audio_packets(bool is_final) { - // Init audio buffers / variables - int total_frame_samples = 0; - int frame_position = 0; - int channels_in_frame = 0; - int sample_rate_in_frame = 0; - int samples_in_frame = 0; - ChannelLayout channel_layout_in_frame = LAYOUT_MONO; // default channel layout + // Init audio buffers / variables + int total_frame_samples = 0; + int frame_position = 0; + int channels_in_frame = 0; + int sample_rate_in_frame = 0; + int samples_in_frame = 0; + ChannelLayout channel_layout_in_frame = LAYOUT_MONO; // default channel layout - // Create a new array (to hold all S16 audio samples, for the current queued frames - unsigned int all_queued_samples_size = sizeof(int16_t) * (queued_audio_frames.size() * AVCODEC_MAX_AUDIO_FRAME_SIZE); - int16_t *all_queued_samples = (int16_t *) av_malloc(all_queued_samples_size); - int16_t *all_resampled_samples = NULL; - int16_t *final_samples_planar = NULL; - int16_t *final_samples = NULL; + // Create a new array (to hold all S16 audio samples, for the current queued frames + unsigned int all_queued_samples_size = sizeof(int16_t) * (queued_audio_frames.size() * AVCODEC_MAX_AUDIO_FRAME_SIZE); + int16_t *all_queued_samples = (int16_t *) av_malloc(all_queued_samples_size); + int16_t *all_resampled_samples = NULL; + int16_t *final_samples_planar = NULL; + int16_t *final_samples = NULL; - // Loop through each queued audio frame - while (!queued_audio_frames.empty()) { - // Get front frame (from the queue) - std::shared_ptr frame = queued_audio_frames.front(); + // Loop through each queued audio frame + while (!queued_audio_frames.empty()) { + // Get front frame (from the queue) + std::shared_ptr frame = queued_audio_frames.front(); - // Get the audio details from this frame - sample_rate_in_frame = frame->SampleRate(); - samples_in_frame = frame->GetAudioSamplesCount(); - channels_in_frame = frame->GetAudioChannelsCount(); - channel_layout_in_frame = frame->ChannelsLayout(); + // Get the audio details from this frame + sample_rate_in_frame = frame->SampleRate(); + samples_in_frame = frame->GetAudioSamplesCount(); + channels_in_frame = frame->GetAudioChannelsCount(); + channel_layout_in_frame = frame->ChannelsLayout(); - // Get audio sample array - float *frame_samples_float = NULL; - // Get samples interleaved together (c1 c2 c1 c2 c1 c2) - frame_samples_float = frame->GetInterleavedAudioSamples(sample_rate_in_frame, NULL, &samples_in_frame); + // Get audio sample array + float *frame_samples_float = NULL; + // Get samples interleaved together (c1 c2 c1 c2 c1 c2) + frame_samples_float = frame->GetInterleavedAudioSamples(sample_rate_in_frame, NULL, &samples_in_frame); - // Calculate total samples - total_frame_samples = samples_in_frame * channels_in_frame; + // Calculate total samples + total_frame_samples = samples_in_frame * channels_in_frame; - // Translate audio sample values back to 16 bit integers with saturation - const int16_t max16 = 32767; - const int16_t min16 = -32768; - for (int s = 0; s < total_frame_samples; s++, frame_position++) { - float valF = frame_samples_float[s] * (1 << 15); - int16_t conv; - if (valF > max16) { - conv = max16; - } else if (valF < min16) { - conv = min16; - } else { - conv = int(valF + 32768.5) - 32768; // +0.5 is for rounding - } + // Translate audio sample values back to 16 bit integers with saturation + const int16_t max16 = 32767; + const int16_t min16 = -32768; + for (int s = 0; s < total_frame_samples; s++, frame_position++) { + float valF = frame_samples_float[s] * (1 << 15); + int16_t conv; + if (valF > max16) { + conv = max16; + } else if (valF < min16) { + conv = min16; + } else { + conv = int(valF + 32768.5) - 32768; // +0.5 is for rounding + } - // Copy into buffer - all_queued_samples[frame_position] = conv; - } + // Copy into buffer + all_queued_samples[frame_position] = conv; + } - // Deallocate float array - delete[] frame_samples_float; + // Deallocate float array + delete[] frame_samples_float; - // Remove front item - queued_audio_frames.pop_front(); + // Remove front item + queued_audio_frames.pop_front(); - } // end while + } // end while - // Update total samples (since we've combined all queued frames) - total_frame_samples = frame_position; - int remaining_frame_samples = total_frame_samples; - int samples_position = 0; + // Update total samples (since we've combined all queued frames) + total_frame_samples = frame_position; + int remaining_frame_samples = total_frame_samples; + int samples_position = 0; - ZmqLogger::Instance()->AppendDebugMethod( - "FFmpegWriter::write_audio_packets", - "is_final", is_final, - "total_frame_samples", total_frame_samples, - "channel_layout_in_frame", channel_layout_in_frame, - "channels_in_frame", channels_in_frame, - "samples_in_frame", samples_in_frame, - "LAYOUT_MONO", LAYOUT_MONO); + ZmqLogger::Instance()->AppendDebugMethod( + "FFmpegWriter::write_audio_packets", + "is_final", is_final, + "total_frame_samples", total_frame_samples, + "channel_layout_in_frame", channel_layout_in_frame, + "channels_in_frame", channels_in_frame, + "samples_in_frame", samples_in_frame, + "LAYOUT_MONO", LAYOUT_MONO); - // Keep track of the original sample format - AVSampleFormat output_sample_fmt = audio_codec_ctx->sample_fmt; + // Keep track of the original sample format + AVSampleFormat output_sample_fmt = audio_codec_ctx->sample_fmt; - AVFrame *audio_frame = NULL; - if (!is_final) { - // Create input frame (and allocate arrays) - audio_frame = AV_ALLOCATE_FRAME(); - AV_RESET_FRAME(audio_frame); - audio_frame->nb_samples = total_frame_samples / channels_in_frame; + AVFrame *audio_frame = NULL; + if (!is_final) { + // Create input frame (and allocate arrays) + audio_frame = AV_ALLOCATE_FRAME(); + AV_RESET_FRAME(audio_frame); + audio_frame->nb_samples = total_frame_samples / channels_in_frame; - // Fill input frame with sample data - int error_code = avcodec_fill_audio_frame(audio_frame, channels_in_frame, AV_SAMPLE_FMT_S16, (uint8_t *) all_queued_samples, all_queued_samples_size, 0); - if (error_code < 0) { - ZmqLogger::Instance()->AppendDebugMethod( - "FFmpegWriter::write_audio_packets ERROR [" - + av_err2string(error_code) + "]", - "error_code", error_code); - } + // Fill input frame with sample data + int error_code = avcodec_fill_audio_frame(audio_frame, channels_in_frame, AV_SAMPLE_FMT_S16, (uint8_t *) all_queued_samples, all_queued_samples_size, 0); + if (error_code < 0) { + ZmqLogger::Instance()->AppendDebugMethod( + "FFmpegWriter::write_audio_packets ERROR [" + + av_err2string(error_code) + "]", + "error_code", error_code); + } - // Do not convert audio to planar format (yet). We need to keep everything interleaved at this point. - switch (audio_codec_ctx->sample_fmt) { - case AV_SAMPLE_FMT_FLTP: { - output_sample_fmt = AV_SAMPLE_FMT_FLT; - break; - } - case AV_SAMPLE_FMT_S32P: { - output_sample_fmt = AV_SAMPLE_FMT_S32; - break; - } - case AV_SAMPLE_FMT_S16P: { - output_sample_fmt = AV_SAMPLE_FMT_S16; - break; - } - case AV_SAMPLE_FMT_U8P: { - output_sample_fmt = AV_SAMPLE_FMT_U8; - break; - } - default: { - // This is only here to silence unused-enum warnings - break; - } - } + // Do not convert audio to planar format (yet). We need to keep everything interleaved at this point. + switch (audio_codec_ctx->sample_fmt) { + case AV_SAMPLE_FMT_FLTP: { + output_sample_fmt = AV_SAMPLE_FMT_FLT; + break; + } + case AV_SAMPLE_FMT_S32P: { + output_sample_fmt = AV_SAMPLE_FMT_S32; + break; + } + case AV_SAMPLE_FMT_S16P: { + output_sample_fmt = AV_SAMPLE_FMT_S16; + break; + } + case AV_SAMPLE_FMT_U8P: { + output_sample_fmt = AV_SAMPLE_FMT_U8; + break; + } + default: { + // This is only here to silence unused-enum warnings + break; + } + } - // Update total samples & input frame size (due to bigger or smaller data types) - total_frame_samples *= (float(info.sample_rate) / sample_rate_in_frame); // adjust for different byte sizes - total_frame_samples *= (float(info.channels) / channels_in_frame); // adjust for different # of channels + // Update total samples & input frame size (due to bigger or smaller data types) + total_frame_samples *= (float(info.sample_rate) / sample_rate_in_frame); // adjust for different byte sizes + total_frame_samples *= (float(info.channels) / channels_in_frame); // adjust for different # of channels - // Create output frame (and allocate arrays) - AVFrame *audio_converted = AV_ALLOCATE_FRAME(); - AV_RESET_FRAME(audio_converted); - audio_converted->nb_samples = total_frame_samples / channels_in_frame; - av_samples_alloc(audio_converted->data, audio_converted->linesize, info.channels, audio_converted->nb_samples, output_sample_fmt, 0); + // Create output frame (and allocate arrays) + AVFrame *audio_converted = AV_ALLOCATE_FRAME(); + AV_RESET_FRAME(audio_converted); + audio_converted->nb_samples = total_frame_samples / channels_in_frame; + av_samples_alloc(audio_converted->data, audio_converted->linesize, info.channels, audio_converted->nb_samples, output_sample_fmt, 0); - ZmqLogger::Instance()->AppendDebugMethod( - "FFmpegWriter::write_audio_packets (1st resampling)", - "in_sample_fmt", AV_SAMPLE_FMT_S16, - "out_sample_fmt", output_sample_fmt, - "in_sample_rate", sample_rate_in_frame, - "out_sample_rate", info.sample_rate, - "in_channels", channels_in_frame, - "out_channels", info.channels); + ZmqLogger::Instance()->AppendDebugMethod( + "FFmpegWriter::write_audio_packets (1st resampling)", + "in_sample_fmt", AV_SAMPLE_FMT_S16, + "out_sample_fmt", output_sample_fmt, + "in_sample_rate", sample_rate_in_frame, + "out_sample_rate", info.sample_rate, + "in_channels", channels_in_frame, + "out_channels", info.channels); - // setup resample context - if (!avr) { - avr = SWR_ALLOC(); - av_opt_set_int(avr, "in_channel_layout", channel_layout_in_frame, 0); - av_opt_set_int(avr, "out_channel_layout", info.channel_layout, 0); - av_opt_set_int(avr, "in_sample_fmt", AV_SAMPLE_FMT_S16, 0); - av_opt_set_int(avr, "out_sample_fmt", output_sample_fmt, 0); // planar not allowed here - av_opt_set_int(avr, "in_sample_rate", sample_rate_in_frame, 0); - av_opt_set_int(avr, "out_sample_rate", info.sample_rate, 0); - av_opt_set_int(avr, "in_channels", channels_in_frame, 0); - av_opt_set_int(avr, "out_channels", info.channels, 0); - SWR_INIT(avr); - } - // Convert audio samples - int nb_samples = SWR_CONVERT( - avr, // audio resample context - audio_converted->data, // output data pointers - audio_converted->linesize[0], // output plane size, in bytes. (0 if unknown) - audio_converted->nb_samples, // maximum number of samples that the output buffer can hold - audio_frame->data, // input data pointers - audio_frame->linesize[0], // input plane size, in bytes (0 if unknown) - audio_frame->nb_samples // number of input samples to convert - ); + // setup resample context + if (!avr) { + avr = SWR_ALLOC(); + av_opt_set_int(avr, "in_channel_layout", channel_layout_in_frame, 0); + av_opt_set_int(avr, "out_channel_layout", info.channel_layout, 0); + av_opt_set_int(avr, "in_sample_fmt", AV_SAMPLE_FMT_S16, 0); + av_opt_set_int(avr, "out_sample_fmt", output_sample_fmt, 0); // planar not allowed here + av_opt_set_int(avr, "in_sample_rate", sample_rate_in_frame, 0); + av_opt_set_int(avr, "out_sample_rate", info.sample_rate, 0); + av_opt_set_int(avr, "in_channels", channels_in_frame, 0); + av_opt_set_int(avr, "out_channels", info.channels, 0); + SWR_INIT(avr); + } + // Convert audio samples + int nb_samples = SWR_CONVERT( + avr, // audio resample context + audio_converted->data, // output data pointers + audio_converted->linesize[0], // output plane size, in bytes. (0 if unknown) + audio_converted->nb_samples, // maximum number of samples that the output buffer can hold + audio_frame->data, // input data pointers + audio_frame->linesize[0], // input plane size, in bytes (0 if unknown) + audio_frame->nb_samples // number of input samples to convert + ); - // Set remaining samples - remaining_frame_samples = total_frame_samples; + // Set remaining samples + remaining_frame_samples = total_frame_samples; - // Create a new array (to hold all resampled S16 audio samples) - all_resampled_samples = (int16_t *) av_malloc( - sizeof(int16_t) * nb_samples * info.channels - * (av_get_bytes_per_sample(output_sample_fmt) / - av_get_bytes_per_sample(AV_SAMPLE_FMT_S16) ) - ); + // Create a new array (to hold all resampled S16 audio samples) + all_resampled_samples = (int16_t *) av_malloc( + sizeof(int16_t) * nb_samples * info.channels + * (av_get_bytes_per_sample(output_sample_fmt) / + av_get_bytes_per_sample(AV_SAMPLE_FMT_S16) ) + ); - // Copy audio samples over original samples - memcpy(all_resampled_samples, audio_converted->data[0], - static_cast(nb_samples) - * info.channels - * av_get_bytes_per_sample(output_sample_fmt)); + // Copy audio samples over original samples + memcpy(all_resampled_samples, audio_converted->data[0], + static_cast(nb_samples) + * info.channels + * av_get_bytes_per_sample(output_sample_fmt)); - // Remove converted audio - av_freep(&(audio_frame->data[0])); - AV_FREE_FRAME(&audio_frame); - av_freep(&audio_converted->data[0]); - AV_FREE_FRAME(&audio_converted); - all_queued_samples = NULL; // this array cleared with above call + // Remove converted audio + av_freep(&(audio_frame->data[0])); + AV_FREE_FRAME(&audio_frame); + av_freep(&audio_converted->data[0]); + AV_FREE_FRAME(&audio_converted); + all_queued_samples = NULL; // this array cleared with above call - ZmqLogger::Instance()->AppendDebugMethod( - "FFmpegWriter::write_audio_packets (Successfully completed 1st resampling)", - "nb_samples", nb_samples, - "remaining_frame_samples", remaining_frame_samples); - } + ZmqLogger::Instance()->AppendDebugMethod( + "FFmpegWriter::write_audio_packets (Successfully completed 1st resampling)", + "nb_samples", nb_samples, + "remaining_frame_samples", remaining_frame_samples); + } - // Loop until no more samples - while (remaining_frame_samples > 0 || is_final) { - // Get remaining samples needed for this packet - int remaining_packet_samples = (audio_input_frame_size * info.channels) - audio_input_position; + // Loop until no more samples + while (remaining_frame_samples > 0 || is_final) { + // Get remaining samples needed for this packet + int remaining_packet_samples = (audio_input_frame_size * info.channels) - audio_input_position; - // Determine how many samples we need - int diff = 0; - if (remaining_frame_samples >= remaining_packet_samples) { - diff = remaining_packet_samples; - } else { - diff = remaining_frame_samples; - } + // Determine how many samples we need + int diff = 0; + if (remaining_frame_samples >= remaining_packet_samples) { + diff = remaining_packet_samples; + } else { + diff = remaining_frame_samples; + } - // Copy frame samples into the packet samples array - if (!is_final) - //TODO: Make this more sane - memcpy( - samples + (audio_input_position - * (av_get_bytes_per_sample(output_sample_fmt) / - av_get_bytes_per_sample(AV_SAMPLE_FMT_S16) ) - ), - all_resampled_samples + samples_position, - static_cast(diff) - * av_get_bytes_per_sample(output_sample_fmt) - ); + // Copy frame samples into the packet samples array + if (!is_final) + //TODO: Make this more sane + memcpy( + samples + (audio_input_position + * (av_get_bytes_per_sample(output_sample_fmt) / + av_get_bytes_per_sample(AV_SAMPLE_FMT_S16) ) + ), + all_resampled_samples + samples_position, + static_cast(diff) + * av_get_bytes_per_sample(output_sample_fmt) + ); - // Increment counters - audio_input_position += diff; - samples_position += diff * (av_get_bytes_per_sample(output_sample_fmt) / av_get_bytes_per_sample(AV_SAMPLE_FMT_S16)); - remaining_frame_samples -= diff; + // Increment counters + audio_input_position += diff; + samples_position += diff * (av_get_bytes_per_sample(output_sample_fmt) / av_get_bytes_per_sample(AV_SAMPLE_FMT_S16)); + remaining_frame_samples -= diff; - // Do we have enough samples to proceed? - if (audio_input_position < (audio_input_frame_size * info.channels) && !is_final) - // Not enough samples to encode... so wait until the next frame - break; + // Do we have enough samples to proceed? + if (audio_input_position < (audio_input_frame_size * info.channels) && !is_final) + // Not enough samples to encode... so wait until the next frame + break; - // Convert to planar (if needed by audio codec) - AVFrame *frame_final = AV_ALLOCATE_FRAME(); - AV_RESET_FRAME(frame_final); - if (av_sample_fmt_is_planar(audio_codec_ctx->sample_fmt)) { - ZmqLogger::Instance()->AppendDebugMethod( - "FFmpegWriter::write_audio_packets (2nd resampling for Planar formats)", - "in_sample_fmt", output_sample_fmt, - "out_sample_fmt", audio_codec_ctx->sample_fmt, - "in_sample_rate", info.sample_rate, - "out_sample_rate", info.sample_rate, - "in_channels", info.channels, - "out_channels", info.channels - ); + // Convert to planar (if needed by audio codec) + AVFrame *frame_final = AV_ALLOCATE_FRAME(); + AV_RESET_FRAME(frame_final); + if (av_sample_fmt_is_planar(audio_codec_ctx->sample_fmt)) { + ZmqLogger::Instance()->AppendDebugMethod( + "FFmpegWriter::write_audio_packets (2nd resampling for Planar formats)", + "in_sample_fmt", output_sample_fmt, + "out_sample_fmt", audio_codec_ctx->sample_fmt, + "in_sample_rate", info.sample_rate, + "out_sample_rate", info.sample_rate, + "in_channels", info.channels, + "out_channels", info.channels + ); - // setup resample context - if (!avr_planar) { - avr_planar = SWR_ALLOC(); - av_opt_set_int(avr_planar, "in_channel_layout", info.channel_layout, 0); - av_opt_set_int(avr_planar, "out_channel_layout", info.channel_layout, 0); - av_opt_set_int(avr_planar, "in_sample_fmt", output_sample_fmt, 0); - av_opt_set_int(avr_planar, "out_sample_fmt", audio_codec_ctx->sample_fmt, 0); // planar not allowed here - av_opt_set_int(avr_planar, "in_sample_rate", info.sample_rate, 0); - av_opt_set_int(avr_planar, "out_sample_rate", info.sample_rate, 0); - av_opt_set_int(avr_planar, "in_channels", info.channels, 0); - av_opt_set_int(avr_planar, "out_channels", info.channels, 0); - SWR_INIT(avr_planar); - } + // setup resample context + if (!avr_planar) { + avr_planar = SWR_ALLOC(); + av_opt_set_int(avr_planar, "in_channel_layout", info.channel_layout, 0); + av_opt_set_int(avr_planar, "out_channel_layout", info.channel_layout, 0); + av_opt_set_int(avr_planar, "in_sample_fmt", output_sample_fmt, 0); + av_opt_set_int(avr_planar, "out_sample_fmt", audio_codec_ctx->sample_fmt, 0); // planar not allowed here + av_opt_set_int(avr_planar, "in_sample_rate", info.sample_rate, 0); + av_opt_set_int(avr_planar, "out_sample_rate", info.sample_rate, 0); + av_opt_set_int(avr_planar, "in_channels", info.channels, 0); + av_opt_set_int(avr_planar, "out_channels", info.channels, 0); + SWR_INIT(avr_planar); + } - // Create input frame (and allocate arrays) - audio_frame = AV_ALLOCATE_FRAME(); - AV_RESET_FRAME(audio_frame); - audio_frame->nb_samples = audio_input_position / info.channels; + // Create input frame (and allocate arrays) + audio_frame = AV_ALLOCATE_FRAME(); + AV_RESET_FRAME(audio_frame); + audio_frame->nb_samples = audio_input_position / info.channels; - // Create a new array - final_samples_planar = (int16_t *) av_malloc( - sizeof(int16_t) * audio_frame->nb_samples * info.channels - * (av_get_bytes_per_sample(output_sample_fmt) / - av_get_bytes_per_sample(AV_SAMPLE_FMT_S16) ) - ); + // Create a new array + final_samples_planar = (int16_t *) av_malloc( + sizeof(int16_t) * audio_frame->nb_samples * info.channels + * (av_get_bytes_per_sample(output_sample_fmt) / + av_get_bytes_per_sample(AV_SAMPLE_FMT_S16) ) + ); - // Copy audio into buffer for frame - memcpy(final_samples_planar, samples, - static_cast(audio_frame->nb_samples) - * info.channels - * av_get_bytes_per_sample(output_sample_fmt)); + // Copy audio into buffer for frame + memcpy(final_samples_planar, samples, + static_cast(audio_frame->nb_samples) + * info.channels + * av_get_bytes_per_sample(output_sample_fmt)); - // Fill input frame with sample data - avcodec_fill_audio_frame(audio_frame, info.channels, output_sample_fmt, - (uint8_t *) final_samples_planar, audio_encoder_buffer_size, 0); + // Fill input frame with sample data + avcodec_fill_audio_frame(audio_frame, info.channels, output_sample_fmt, + (uint8_t *) final_samples_planar, audio_encoder_buffer_size, 0); - // Create output frame (and allocate arrays) - frame_final->nb_samples = audio_input_frame_size; - frame_final->channels = info.channels; - frame_final->format = audio_codec_ctx->sample_fmt; - frame_final->channel_layout = info.channel_layout; - av_samples_alloc(frame_final->data, frame_final->linesize, info.channels, - frame_final->nb_samples, audio_codec_ctx->sample_fmt, 0); + // Create output frame (and allocate arrays) + frame_final->nb_samples = audio_input_frame_size; + frame_final->channels = info.channels; + frame_final->format = audio_codec_ctx->sample_fmt; + frame_final->channel_layout = info.channel_layout; + av_samples_alloc(frame_final->data, frame_final->linesize, info.channels, + frame_final->nb_samples, audio_codec_ctx->sample_fmt, 0); - // Convert audio samples - int nb_samples = SWR_CONVERT( - avr_planar, // audio resample context - frame_final->data, // output data pointers - frame_final->linesize[0], // output plane size, in bytes. (0 if unknown) - frame_final->nb_samples, // maximum number of samples that the output buffer can hold - audio_frame->data, // input data pointers - audio_frame->linesize[0], // input plane size, in bytes (0 if unknown) - audio_frame->nb_samples // number of input samples to convert - ); + // Convert audio samples + int nb_samples = SWR_CONVERT( + avr_planar, // audio resample context + frame_final->data, // output data pointers + frame_final->linesize[0], // output plane size, in bytes. (0 if unknown) + frame_final->nb_samples, // maximum number of samples that the output buffer can hold + audio_frame->data, // input data pointers + audio_frame->linesize[0], // input plane size, in bytes (0 if unknown) + audio_frame->nb_samples // number of input samples to convert + ); - // Copy audio samples over original samples - const auto copy_length = static_cast(nb_samples) - * av_get_bytes_per_sample(audio_codec_ctx->sample_fmt) - * info.channels; + // Copy audio samples over original samples + const auto copy_length = static_cast(nb_samples) + * av_get_bytes_per_sample(audio_codec_ctx->sample_fmt) + * info.channels; - if (nb_samples > 0) - memcpy(samples, frame_final->data[0], copy_length); + if (nb_samples > 0) + memcpy(samples, frame_final->data[0], copy_length); - // deallocate AVFrame - av_freep(&(audio_frame->data[0])); - AV_FREE_FRAME(&audio_frame); - all_queued_samples = NULL; // this array cleared with above call + // deallocate AVFrame + av_freep(&(audio_frame->data[0])); + AV_FREE_FRAME(&audio_frame); + all_queued_samples = NULL; // this array cleared with above call - ZmqLogger::Instance()->AppendDebugMethod( - "FFmpegWriter::write_audio_packets (Successfully completed 2nd resampling for Planar formats)", - "nb_samples", nb_samples); + ZmqLogger::Instance()->AppendDebugMethod( + "FFmpegWriter::write_audio_packets (Successfully completed 2nd resampling for Planar formats)", + "nb_samples", nb_samples); - } else { - // Create a new array - const auto buf_size = static_cast(audio_input_position) - * (av_get_bytes_per_sample(audio_codec_ctx->sample_fmt) / - av_get_bytes_per_sample(AV_SAMPLE_FMT_S16) - ); - final_samples = reinterpret_cast( - av_malloc(sizeof(int16_t) * buf_size)); + } else { + // Create a new array + const auto buf_size = static_cast(audio_input_position) + * (av_get_bytes_per_sample(audio_codec_ctx->sample_fmt) / + av_get_bytes_per_sample(AV_SAMPLE_FMT_S16) + ); + final_samples = reinterpret_cast( + av_malloc(sizeof(int16_t) * buf_size)); - // Copy audio into buffer for frame - memcpy(final_samples, samples, - audio_input_position * av_get_bytes_per_sample(audio_codec_ctx->sample_fmt)); + // Copy audio into buffer for frame + memcpy(final_samples, samples, + audio_input_position * av_get_bytes_per_sample(audio_codec_ctx->sample_fmt)); - // Init the nb_samples property - frame_final->nb_samples = audio_input_frame_size; + // Init the nb_samples property + frame_final->nb_samples = audio_input_frame_size; - // Fill the final_frame AVFrame with audio (non planar) - avcodec_fill_audio_frame(frame_final, audio_codec_ctx->channels, - audio_codec_ctx->sample_fmt, (uint8_t *) final_samples, - audio_encoder_buffer_size, 0); - } + // Fill the final_frame AVFrame with audio (non planar) + avcodec_fill_audio_frame(frame_final, audio_codec_ctx->channels, + audio_codec_ctx->sample_fmt, (uint8_t *) final_samples, + audio_encoder_buffer_size, 0); + } - // Set the AVFrame's PTS - frame_final->pts = audio_timestamp; + // Set the AVFrame's PTS + frame_final->pts = audio_timestamp; - // Init the packet + // Init the packet #if IS_FFMPEG_3_2 - AVPacket* pkt = av_packet_alloc(); + AVPacket* pkt = av_packet_alloc(); #else - AVPacket* pkt; - av_init_packet(pkt); + AVPacket* pkt; + av_init_packet(pkt); #endif - pkt->data = audio_encoder_buffer; - pkt->size = audio_encoder_buffer_size; + pkt->data = audio_encoder_buffer; + pkt->size = audio_encoder_buffer_size; - // Set the packet's PTS prior to encoding - pkt->pts = pkt->dts = audio_timestamp; + // Set the packet's PTS prior to encoding + pkt->pts = pkt->dts = audio_timestamp; - /* encode the audio samples */ - int got_packet_ptr = 0; + /* encode the audio samples */ + int got_packet_ptr = 0; #if IS_FFMPEG_3_2 - // Encode audio (latest version of FFmpeg) - int error_code; - int ret = 0; - int frame_finished = 0; - error_code = ret = avcodec_send_frame(audio_codec_ctx, frame_final); - if (ret < 0 && ret != AVERROR(EINVAL) && ret != AVERROR_EOF) { - avcodec_send_frame(audio_codec_ctx, NULL); - } - else { - if (ret >= 0) - pkt->size = 0; - ret = avcodec_receive_packet(audio_codec_ctx, pkt); - if (ret >= 0) - frame_finished = 1; - if(ret == AVERROR(EINVAL) || ret == AVERROR_EOF) { - avcodec_flush_buffers(audio_codec_ctx); - ret = 0; - } - if (ret >= 0) { - ret = frame_finished; - } - } - if (!pkt->data && !frame_finished) - { - ret = -1; - } - got_packet_ptr = ret; + // Encode audio (latest version of FFmpeg) + int error_code; + int ret = 0; + int frame_finished = 0; + error_code = ret = avcodec_send_frame(audio_codec_ctx, frame_final); + if (ret < 0 && ret != AVERROR(EINVAL) && ret != AVERROR_EOF) { + avcodec_send_frame(audio_codec_ctx, NULL); + } + else { + if (ret >= 0) + pkt->size = 0; + ret = avcodec_receive_packet(audio_codec_ctx, pkt); + if (ret >= 0) + frame_finished = 1; + if(ret == AVERROR(EINVAL) || ret == AVERROR_EOF) { + avcodec_flush_buffers(audio_codec_ctx); + ret = 0; + } + if (ret >= 0) { + ret = frame_finished; + } + } + if (!pkt->data && !frame_finished) + { + ret = -1; + } + got_packet_ptr = ret; #else - // Encode audio (older versions of FFmpeg) - int error_code = avcodec_encode_audio2(audio_codec_ctx, pkt, frame_final, &got_packet_ptr); + // Encode audio (older versions of FFmpeg) + int error_code = avcodec_encode_audio2(audio_codec_ctx, pkt, frame_final, &got_packet_ptr); #endif - /* if zero size, it means the image was buffered */ - if (error_code == 0 && got_packet_ptr) { + /* if zero size, it means the image was buffered */ + if (error_code == 0 && got_packet_ptr) { - // Since the PTS can change during encoding, set the value again. This seems like a huge hack, - // but it fixes lots of PTS related issues when I do this. - pkt->pts = pkt->dts = audio_timestamp; + // Since the PTS can change during encoding, set the value again. This seems like a huge hack, + // but it fixes lots of PTS related issues when I do this. + pkt->pts = pkt->dts = audio_timestamp; - // Scale the PTS to the audio stream timebase (which is sometimes different than the codec's timebase) - av_packet_rescale_ts(pkt, audio_codec_ctx->time_base, audio_st->time_base); + // Scale the PTS to the audio stream timebase (which is sometimes different than the codec's timebase) + av_packet_rescale_ts(pkt, audio_codec_ctx->time_base, audio_st->time_base); - // set stream - pkt->stream_index = audio_st->index; - pkt->flags |= AV_PKT_FLAG_KEY; + // set stream + pkt->stream_index = audio_st->index; + pkt->flags |= AV_PKT_FLAG_KEY; - /* write the compressed frame in the media file */ - error_code = av_interleaved_write_frame(oc, pkt); - } + /* write the compressed frame in the media file */ + error_code = av_interleaved_write_frame(oc, pkt); + } - if (error_code < 0) { - ZmqLogger::Instance()->AppendDebugMethod( - "FFmpegWriter::write_audio_packets ERROR [" - + av_err2string(error_code) + "]", - "error_code", error_code); - } + if (error_code < 0) { + ZmqLogger::Instance()->AppendDebugMethod( + "FFmpegWriter::write_audio_packets ERROR [" + + av_err2string(error_code) + "]", + "error_code", error_code); + } - // Increment PTS (no pkt.duration, so calculate with maths) - audio_timestamp += FFMIN(audio_input_frame_size, audio_input_position); + // Increment PTS (no pkt.duration, so calculate with maths) + audio_timestamp += FFMIN(audio_input_frame_size, audio_input_position); - // deallocate AVFrame - av_freep(&(frame_final->data[0])); - AV_FREE_FRAME(&frame_final); + // deallocate AVFrame + av_freep(&(frame_final->data[0])); + AV_FREE_FRAME(&frame_final); - // deallocate memory for packet - AV_FREE_PACKET(pkt); + // deallocate memory for packet + AV_FREE_PACKET(pkt); - // Reset position - audio_input_position = 0; - is_final = false; - } + // Reset position + audio_input_position = 0; + is_final = false; + } - // Delete arrays (if needed) - if (all_resampled_samples) { - av_freep(&all_resampled_samples); - all_resampled_samples = NULL; - } - if (all_queued_samples) { - av_freep(&all_queued_samples); - all_queued_samples = NULL; - } + // Delete arrays (if needed) + if (all_resampled_samples) { + av_freep(&all_resampled_samples); + all_resampled_samples = NULL; + } + if (all_queued_samples) { + av_freep(&all_queued_samples); + all_queued_samples = NULL; + } } // Allocate an AVFrame object @@ -2057,69 +2066,69 @@ AVFrame *FFmpegWriter::allocate_avframe(PixelFormat pix_fmt, int width, int heig // process video frame void FFmpegWriter::process_video_packet(std::shared_ptr frame) { - // Determine the height & width of the source image - int source_image_width = frame->GetWidth(); - int source_image_height = frame->GetHeight(); + // Determine the height & width of the source image + int source_image_width = frame->GetWidth(); + int source_image_height = frame->GetHeight(); - // Do nothing if size is 1x1 (i.e. no image in this frame) - if (source_image_height == 1 && source_image_width == 1) - return; + // Do nothing if size is 1x1 (i.e. no image in this frame) + if (source_image_height == 1 && source_image_width == 1) + return; - // Init rescalers (if not initialized yet) - if (image_rescalers.size() == 0) - InitScalers(source_image_width, source_image_height); + // Init rescalers (if not initialized yet) + if (image_rescalers.size() == 0) + InitScalers(source_image_width, source_image_height); - // Get a unique rescaler (for this thread) - SwsContext *scaler = image_rescalers[rescaler_position]; - rescaler_position++; - if (rescaler_position == num_of_rescalers) - rescaler_position = 0; + // Get a unique rescaler (for this thread) + SwsContext *scaler = image_rescalers[rescaler_position]; + rescaler_position++; + if (rescaler_position == num_of_rescalers) + rescaler_position = 0; - // Allocate an RGB frame & final output frame - int bytes_source = 0; - int bytes_final = 0; - AVFrame *frame_source = NULL; - const uchar *pixels = NULL; + // Allocate an RGB frame & final output frame + int bytes_source = 0; + int bytes_final = 0; + AVFrame *frame_source = NULL; + const uchar *pixels = NULL; - // Get a list of pixels from source image - pixels = frame->GetPixels(); + // Get a list of pixels from source image + pixels = frame->GetPixels(); - // Init AVFrame for source image & final (converted image) - frame_source = allocate_avframe(PIX_FMT_RGBA, source_image_width, source_image_height, &bytes_source, (uint8_t *) pixels); + // Init AVFrame for source image & final (converted image) + frame_source = allocate_avframe(PIX_FMT_RGBA, source_image_width, source_image_height, &bytes_source, (uint8_t *) pixels); #if IS_FFMPEG_3_2 - AVFrame *frame_final; + AVFrame *frame_final; #if USE_HW_ACCEL - if (hw_en_on && hw_en_supported) { - frame_final = allocate_avframe(AV_PIX_FMT_NV12, info.width, info.height, &bytes_final, NULL); - } else + if (hw_en_on && hw_en_supported) { + frame_final = allocate_avframe(AV_PIX_FMT_NV12, info.width, info.height, &bytes_final, NULL); + } else #endif // USE_HW_ACCEL - { - frame_final = allocate_avframe( - (AVPixelFormat)(video_st->codecpar->format), - info.width, info.height, &bytes_final, NULL - ); - } + { + frame_final = allocate_avframe( + (AVPixelFormat)(video_st->codecpar->format), + info.width, info.height, &bytes_final, NULL + ); + } #else - AVFrame *frame_final = allocate_avframe(video_codec_ctx->pix_fmt, info.width, info.height, &bytes_final, NULL); + AVFrame *frame_final = allocate_avframe(video_codec_ctx->pix_fmt, info.width, info.height, &bytes_final, NULL); #endif // IS_FFMPEG_3_2 - // Fill with data - AV_COPY_PICTURE_DATA(frame_source, (uint8_t *) pixels, PIX_FMT_RGBA, source_image_width, source_image_height); - ZmqLogger::Instance()->AppendDebugMethod( - "FFmpegWriter::process_video_packet", - "frame->number", frame->number, - "bytes_source", bytes_source, - "bytes_final", bytes_final); + // Fill with data + AV_COPY_PICTURE_DATA(frame_source, (uint8_t *) pixels, PIX_FMT_RGBA, source_image_width, source_image_height); + ZmqLogger::Instance()->AppendDebugMethod( + "FFmpegWriter::process_video_packet", + "frame->number", frame->number, + "bytes_source", bytes_source, + "bytes_final", bytes_final); - // Resize & convert pixel format - sws_scale(scaler, frame_source->data, frame_source->linesize, 0, - source_image_height, frame_final->data, frame_final->linesize); + // Resize & convert pixel format + sws_scale(scaler, frame_source->data, frame_source->linesize, 0, + source_image_height, frame_final->data, frame_final->linesize); - // Add resized AVFrame to av_frames map - add_avframe(frame, frame_final); + // Add resized AVFrame to av_frames map + add_avframe(frame, frame_final); - // Deallocate memory - AV_FREE_FRAME(&frame_source); + // Deallocate memory + AV_FREE_FRAME(&frame_source); } // write video frame @@ -2134,8 +2143,8 @@ bool FFmpegWriter::write_video_packet(std::shared_ptr frame, AVFrame *fra if (AV_GET_CODEC_TYPE(video_st) == AVMEDIA_TYPE_VIDEO && AV_FIND_DECODER_CODEC_ID(video_st) == AV_CODEC_ID_RAWVIDEO) { #else // TODO: Should we have moved away from oc->oformat->flags / AVFMT_RAWPICTURE - // on ffmpeg < 4.0 as well? - // Does AV_CODEC_ID_RAWVIDEO not work in ffmpeg 3.x? + // on ffmpeg < 4.0 as well? + // Does AV_CODEC_ID_RAWVIDEO not work in ffmpeg 3.x? ZmqLogger::Instance()->AppendDebugMethod( "FFmpegWriter::write_video_packet", "frame->number", frame->number, @@ -2151,7 +2160,7 @@ bool FFmpegWriter::write_video_packet(std::shared_ptr frame, AVFrame *fra av_init_packet(pkt); #endif - av_packet_from_data( + av_packet_from_data( pkt, frame_final->data[0], frame_final->linesize[0] * frame_final->height); diff --git a/src/FrameMapper.cpp b/src/FrameMapper.cpp index 0968fc2a..1f01c089 100644 --- a/src/FrameMapper.cpp +++ b/src/FrameMapper.cpp @@ -41,6 +41,9 @@ FrameMapper::FrameMapper(ReaderBase *reader, Fraction target, PulldownType targe info.width = reader->info.width; info.height = reader->info.height; + // Enable/Disable audio (based on settings) + info.has_audio = info.sample_rate > 0 && info.channels > 0; + // Used to toggle odd / even fields field_toggle = true; @@ -60,11 +63,11 @@ FrameMapper::~FrameMapper() { /// Get the current reader ReaderBase* FrameMapper::Reader() { - if (reader) - return reader; - else - // Throw error if reader not initialized - throw ReaderClosed("No Reader has been initialized for FrameMapper. Call Reader(*reader) before calling this method."); + if (reader) + return reader; + else + // Throw error if reader not initialized + throw ReaderClosed("No Reader has been initialized for FrameMapper. Call Reader(*reader) before calling this method."); } void FrameMapper::AddField(int64_t frame) @@ -84,14 +87,14 @@ void FrameMapper::AddField(Field field) // Clear both the fields & frames lists void FrameMapper::Clear() { - // Prevent async calls to the following code - const std::lock_guard lock(getFrameMutex); + // Prevent async calls to the following code + const std::lock_guard lock(getFrameMutex); - // Clear the fields & frames lists - fields.clear(); - fields.shrink_to_fit(); - frames.clear(); - frames.shrink_to_fit(); + // Clear the fields & frames lists + fields.clear(); + fields.shrink_to_fit(); + frames.clear(); + frames.shrink_to_fit(); } // Use the original and target frame rates and a pull-down technique to create @@ -114,14 +117,14 @@ void FrameMapper::Init() Clear(); // Find parent position (if any) - Clip *parent = (Clip *) ParentClip(); - if (parent) { - parent_position = parent->Position(); - parent_start = parent->Start(); - } else { - parent_position = 0.0; - parent_start = 0.0; - } + Clip *parent = (Clip *) ParentClip(); + if (parent) { + parent_position = parent->Position(); + parent_start = parent->Start(); + } else { + parent_position = 0.0; + parent_start = 0.0; + } // Mark as not dirty is_dirty = false; @@ -419,19 +422,19 @@ std::shared_ptr FrameMapper::GetFrame(int64_t requested_frame) // Create a scoped lock, allowing only a single thread to run the following code at one time const std::lock_guard lock(getFrameMutex); - // Find parent properties (if any) - Clip *parent = (Clip *) ParentClip(); - if (parent) { - float position = parent->Position(); - float start = parent->Start(); - if (parent_position != position || parent_start != start) { - // Force dirty if parent clip has moved or been trimmed - // since this heavily affects frame #s and audio mappings - is_dirty = true; - } - } + // Find parent properties (if any) + Clip *parent = (Clip *) ParentClip(); + if (parent) { + float position = parent->Position(); + float start = parent->Start(); + if (parent_position != position || parent_start != start) { + // Force dirty if parent clip has moved or been trimmed + // since this heavily affects frame #s and audio mappings + is_dirty = true; + } + } - // Check if mappings are dirty (and need to be recalculated) + // Check if mappings are dirty (and need to be recalculated) if (is_dirty) Init(); @@ -513,9 +516,12 @@ std::shared_ptr FrameMapper::GetFrame(int64_t requested_frame) std::make_shared(*even_frame->GetImage()), false); } + // Determine if reader contains audio samples + bool reader_has_audio = frame->SampleRate() > 0 && frame->GetAudioChannelsCount() > 0; + // Resample audio on frame (if needed) bool need_resampling = false; - if (info.has_audio && + if ((info.has_audio && reader_has_audio) && (info.sample_rate != frame->SampleRate() || info.channels != frame->GetAudioChannelsCount() || info.channel_layout != frame->ChannelsLayout())) @@ -537,7 +543,7 @@ std::shared_ptr FrameMapper::GetFrame(int64_t requested_frame) copy_samples.sample_end += EXTRA_INPUT_SAMPLES; int samples_per_end_frame = Frame::GetSamplesPerFrame(copy_samples.frame_end, original, - reader->info.sample_rate, reader->info.channels); + reader->info.sample_rate, reader->info.channels); if (copy_samples.sample_end >= samples_per_end_frame) { // check for wrapping @@ -553,7 +559,7 @@ std::shared_ptr FrameMapper::GetFrame(int64_t requested_frame) copy_samples.sample_start += EXTRA_INPUT_SAMPLES; int samples_per_start_frame = Frame::GetSamplesPerFrame(copy_samples.frame_start, original, - reader->info.sample_rate, reader->info.channels); + reader->info.sample_rate, reader->info.channels); if (copy_samples.sample_start >= samples_per_start_frame) { // check for wrapping @@ -643,15 +649,15 @@ void FrameMapper::PrintMapping(std::ostream* out) { MappedFrame frame = frames[map - 1]; *out << "Target frame #: " << map - << " mapped to original frame #:\t(" - << frame.Odd.Frame << " odd, " - << frame.Even.Frame << " even)" << std::endl; + << " mapped to original frame #:\t(" + << frame.Odd.Frame << " odd, " + << frame.Even.Frame << " even)" << std::endl; *out << " - Audio samples mapped to frame " - << frame.Samples.frame_start << ":" - << frame.Samples.sample_start << " to frame " - << frame.Samples.frame_end << ":" - << frame.Samples.sample_end << endl; + << frame.Samples.frame_start << ":" + << frame.Samples.sample_start << " to frame " + << frame.Samples.frame_end << ":" + << frame.Samples.sample_end << endl; } } @@ -690,21 +696,21 @@ void FrameMapper::Close() reader->Close(); } - // Clear the fields & frames lists - Clear(); + // Clear the fields & frames lists + Clear(); - // Mark as dirty - is_dirty = true; + // Mark as dirty + is_dirty = true; - // Clear cache - final_cache.Clear(); + // Clear cache + final_cache.Clear(); - // Deallocate resample buffer - if (avr) { - SWR_CLOSE(avr); - SWR_FREE(&avr); - avr = NULL; - } + // Deallocate resample buffer + if (avr) { + SWR_CLOSE(avr); + SWR_FREE(&avr); + avr = NULL; + } } @@ -785,6 +791,9 @@ void FrameMapper::ChangeMapping(Fraction target_fps, PulldownType target_pulldow info.channels = target_channels; info.channel_layout = target_channel_layout; + // Enable/Disable audio (based on settings) + info.has_audio = info.sample_rate > 0 && info.channels > 0; + // Clear cache final_cache.Clear(); @@ -913,28 +922,28 @@ void FrameMapper::ResampleMappedAudio(std::shared_ptr frame, int64_t orig int nb_samples = 0; - // setup resample context - if (!avr) { - avr = SWR_ALLOC(); - av_opt_set_int(avr, "in_channel_layout", channel_layout_in_frame, 0); - av_opt_set_int(avr, "out_channel_layout", info.channel_layout, 0); - av_opt_set_int(avr, "in_sample_fmt", AV_SAMPLE_FMT_S16, 0); - av_opt_set_int(avr, "out_sample_fmt", AV_SAMPLE_FMT_S16, 0); - av_opt_set_int(avr, "in_sample_rate", sample_rate_in_frame, 0); - av_opt_set_int(avr, "out_sample_rate", info.sample_rate, 0); - av_opt_set_int(avr, "in_channels", channels_in_frame, 0); - av_opt_set_int(avr, "out_channels", info.channels, 0); - SWR_INIT(avr); - } + // setup resample context + if (!avr) { + avr = SWR_ALLOC(); + av_opt_set_int(avr, "in_channel_layout", channel_layout_in_frame, 0); + av_opt_set_int(avr, "out_channel_layout", info.channel_layout, 0); + av_opt_set_int(avr, "in_sample_fmt", AV_SAMPLE_FMT_S16, 0); + av_opt_set_int(avr, "out_sample_fmt", AV_SAMPLE_FMT_S16, 0); + av_opt_set_int(avr, "in_sample_rate", sample_rate_in_frame, 0); + av_opt_set_int(avr, "out_sample_rate", info.sample_rate, 0); + av_opt_set_int(avr, "in_channels", channels_in_frame, 0); + av_opt_set_int(avr, "out_channels", info.channels, 0); + SWR_INIT(avr); + } - // Convert audio samples - nb_samples = SWR_CONVERT(avr, // audio resample context - audio_converted->data, // output data pointers - audio_converted->linesize[0], // output plane size, in bytes. (0 if unknown) - audio_converted->nb_samples, // maximum number of samples that the output buffer can hold - audio_frame->data, // input data pointers - audio_frame->linesize[0], // input plane size, in bytes (0 if unknown) - audio_frame->nb_samples); // number of input samples to convert + // Convert audio samples + nb_samples = SWR_CONVERT(avr, // audio resample context + audio_converted->data, // output data pointers + audio_converted->linesize[0], // output plane size, in bytes. (0 if unknown) + audio_converted->nb_samples, // maximum number of samples that the output buffer can hold + audio_frame->data, // input data pointers + audio_frame->linesize[0], // input plane size, in bytes (0 if unknown) + audio_frame->nb_samples); // number of input samples to convert // Create a new array (to hold all resampled S16 audio samples) int16_t* resampled_samples = new int16_t[(nb_samples * info.channels)]; diff --git a/src/Profiles.cpp b/src/Profiles.cpp index 84329910..4ea4e412 100644 --- a/src/Profiles.cpp +++ b/src/Profiles.cpp @@ -10,11 +10,27 @@ // // SPDX-License-Identifier: LGPL-3.0-or-later +#include #include "Profiles.h" #include "Exceptions.h" using namespace openshot; +// default constructor +Profile::Profile() { + // Initialize info values + info.description = ""; + info.height = 0; + info.width = 0; + info.pixel_format = 0; + info.fps.num = 0; + info.fps.den = 0; + info.pixel_ratio.num = 0; + info.pixel_ratio.den = 0; + info.display_ratio.num = 0; + info.display_ratio.den = 0; + info.interlaced_frame = false; +} // @brief Constructor for Profile. // @param path The folder path / location of a profile file @@ -22,21 +38,11 @@ Profile::Profile(std::string path) { bool read_file = false; + // Call default constructor + Profile(); + try { - // Initialize info values - info.description = ""; - info.height = 0; - info.width = 0; - info.pixel_format = 0; - info.fps.num = 0; - info.fps.den = 0; - info.pixel_ratio.num = 0; - info.pixel_ratio.den = 0; - info.display_ratio.num = 0; - info.display_ratio.den = 0; - info.interlaced_frame = false; - QFile inputFile(path.c_str()); if (inputFile.open(QIODevice::ReadOnly)) { @@ -55,8 +61,9 @@ Profile::Profile(std::string path) { int value_int = 0; // update struct (based on line number) - if (setting == "description") + if (setting == "description") { info.description = value; + } else if (setting == "frame_rate_num") { std::stringstream(value) >> value_int; info.fps.num = value_int; @@ -98,7 +105,7 @@ Profile::Profile(std::string path) { info.pixel_format = value_int; } } - read_file = true; + read_file = true; inputFile.close(); } @@ -115,6 +122,81 @@ Profile::Profile(std::string path) { throw InvalidFile("Profile could not be found or loaded (or is invalid).", path); } +// Return a formatted FPS +std::string Profile::formattedFPS(bool include_decimal) { + // Format FPS to use 2 decimals (if needed) + float fps = info.fps.ToFloat(); + std::stringstream fps_string; + if (info.fps.den == 1) { + // For example: 24.0 will become 24 + fps_string << std::fixed << std::setprecision(0) << fps; + } else { + // For example: 29.97002997 will become 29.97 + fps_string << std::fixed << std::setprecision(2) << fps; + // Remove decimal place using QString (for convenience) + if (!include_decimal) { + QString fps_qstring = QString::fromStdString(fps_string.str()); + fps_qstring.replace(".", ""); + fps_string.str(fps_qstring.toStdString()); + } + } + return fps_string.str(); +} + +// Return a unique key of this profile (01920x1080i2997_16-09) +std::string Profile::Key() { + std::stringstream output; + std::string progressive_str = "p"; + if (info.interlaced_frame) { + progressive_str = "i"; + } + std::string fps_string = formattedFPS(false); + output << std::setfill('0') << std::setw(5) << info.width << std::setfill('\0') << "x"; + output << std::setfill('0') << std::setw(4) << info.height << std::setfill('\0') << progressive_str; + output << std::setfill('0') << std::setw(4) << fps_string << std::setfill('\0') << "_"; + output << std::setfill('0') << std::setw(2) << info.display_ratio.num << std::setfill('\0') << "-"; + output << std::setfill('0') << std::setw(2) << info.display_ratio.den << std::setfill('\0'); + return output.str(); +} + +// Return the name of this profile (1920x1080p29.97) +std::string Profile::ShortName() { + std::stringstream output; + std::string progressive_str = "p"; + if (info.interlaced_frame) { + progressive_str = "i"; + } + std::string fps_string = formattedFPS(true); + output << info.width << "x" << info.height << progressive_str << fps_string; + return output.str(); +} + +// Return a longer format name (1920x1080p @ 29.97 fps (16:9)) +std::string Profile::LongName() { + std::stringstream output; + std::string progressive_str = "p"; + if (info.interlaced_frame) { + progressive_str = "i"; + } + std::string fps_string = formattedFPS(true); + output << info.width << "x" << info.height << progressive_str << " @ " << fps_string + << " fps (" << info.display_ratio.num << ":" << info.display_ratio.den << ")"; + return output.str(); +} + +// Return a longer format name (1920x1080p @ 29.97 fps (16:9) HD 1080i 29.97 fps) +std::string Profile::LongNameWithDesc() { + std::stringstream output; + std::string progressive_str = "p"; + if (info.interlaced_frame) { + progressive_str = "i"; + } + std::string fps_string = formattedFPS(true); + output << info.width << "x" << info.height << progressive_str << " @ " << fps_string + << " fps (" << info.display_ratio.num << ":" << info.display_ratio.den << ") " << info.description; + return output.str(); +} + // Generate JSON string of this object std::string Profile::Json() const { diff --git a/src/Profiles.h b/src/Profiles.h index c831df8c..c10372a8 100644 --- a/src/Profiles.h +++ b/src/Profiles.h @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -62,14 +63,90 @@ namespace openshot */ class Profile { + private: + std::string formattedFPS(bool include_decimal); ///< Return a formatted FPS + + /// Less than operator (compare profile objects) + /// Compare # of pixels, then FPS, then DAR + friend bool operator<(const Profile& l, const Profile& r) + { + double left_fps = l.info.fps.ToDouble(); + double right_fps = r.info.fps.ToDouble(); + double left_pixels = l.info.width * l.info.height; + double right_pixels = r.info.width * r.info.height; + double left_dar = l.info.display_ratio.ToDouble(); + double right_dar = r.info.display_ratio.ToDouble(); + + if (left_pixels < right_pixels) { + // less pixels + return true; + } else { + if (left_fps < right_fps) { + // less FPS + return true; + } else { + if (left_dar < right_dar) { + // less DAR + return true; + } else { + return false; + } + } + } + } + + /// Greater than operator (compare profile objects) + /// Compare # of pixels, then FPS, then DAR + friend bool operator>(const Profile& l, const Profile& r) + { + double left_fps = l.info.fps.ToDouble(); + double right_fps = r.info.fps.ToDouble(); + double left_pixels = l.info.width * l.info.height; + double right_pixels = r.info.width * r.info.height; + double left_dar = l.info.display_ratio.ToDouble(); + double right_dar = r.info.display_ratio.ToDouble(); + + if (left_pixels > right_pixels) { + // less pixels + return true; + } else { + if (left_fps > right_fps) { + // less FPS + return true; + } else { + if (left_dar > right_dar) { + // less DAR + return true; + } else { + return false; + } + } + } + } + + /// Equality operator (compare profile objects) + friend bool operator==(const Profile& l, const Profile& r) + { + return std::tie(l.info.width, l.info.height, l.info.fps.num, l.info.fps.den, l.info.display_ratio.num, l.info.display_ratio.den, l.info.interlaced_frame) + == std::tie(r.info.width, r.info.height, r.info.fps.num, r.info.fps.den, r.info.display_ratio.num, r.info.display_ratio.den, r.info.interlaced_frame); + } + public: /// Profile data stored here ProfileInfo info; + /// @brief Default Constructor for Profile. + Profile(); + /// @brief Constructor for Profile. /// @param path The folder path / location of a profile file Profile(std::string path); + std::string Key(); ///< Return a unique key of this profile with padding (01920x1080i2997_16:09) + std::string ShortName(); ///< Return the name of this profile (1920x1080p29.97) + std::string LongName(); ///< Return a longer format name (1920x1080p @ 29.97 fps (16:9)) + std::string LongNameWithDesc(); ///< Return a longer format name with description (1920x1080p @ 29.97 fps (16:9) HD 1080i 29.97 fps) + // Get and Set JSON methods std::string Json() const; ///< Generate JSON string of this object Json::Value JsonValue() const; ///< Generate Json::Value for this object diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index dc8323d7..e8701be6 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -35,6 +35,7 @@ set(OPENSHOT_TESTS FrameMapper KeyFrame Point + Profiles QtImageReader ReaderBase Settings diff --git a/tests/FFmpegWriter.cpp b/tests/FFmpegWriter.cpp index cce3c89d..cbaa7264 100644 --- a/tests/FFmpegWriter.cpp +++ b/tests/FFmpegWriter.cpp @@ -20,6 +20,7 @@ #include "FFmpegReader.h" #include "Fraction.h" #include "Frame.h" +#include "Timeline.h" using namespace std; using namespace openshot; @@ -183,3 +184,52 @@ TEST_CASE( "DisplayInfo", "[libopenshot][ffmpegwriter]" ) // Compare a [0, expected.size()) substring of output to expected CHECK(output.str().substr(0, expected.size()) == expected); } + +TEST_CASE( "Gif", "[libopenshot][ffmpegwriter]" ) +{ + // Reader + std::stringstream path; + path << TEST_MEDIA_PATH << "sintel_trailer-720p.mp4"; + + // Create Gif Clip + Clip clip_video(path.str()); + clip_video.Layer(0); + clip_video.Position(0.0); + clip_video.Open(); + + // Create Timeline w/ 1 Gif Clip (with 0 sample rate, and 0 channels) + openshot::Timeline t(1280, 720, Fraction(30,1), 0, 0, LAYOUT_MONO); + t.AddClip(&clip_video); + t.Open(); + + /* WRITER ---------------- */ + FFmpegWriter w("output1.gif"); + + // Set options (no audio options are set) + w.SetVideoOptions(true, "gif", Fraction(24,1), 1280, 720, Fraction(1,1), false, false, 15000000); + + // Create streams + w.PrepareStreams(); + + // Open writer + w.Open(); + + // Write some frames + w.WriteFrame(&t, 1, 60); + + // Close writer & reader + w.Close(); + t.Close(); + + FFmpegReader r1("output1.gif"); + r1.Open(); + + // Verify various settings on new Gif + CHECK(r1.GetFrame(1)->GetAudioChannelsCount() == 0); + CHECK(r1.GetFrame(1)->GetAudioSamplesCount() == 0); + CHECK(r1.info.fps.num == 24); + CHECK(r1.info.fps.den == 1); + + // Close reader + r1.Close(); +} diff --git a/tests/Profiles.cpp b/tests/Profiles.cpp new file mode 100644 index 00000000..05268dd1 --- /dev/null +++ b/tests/Profiles.cpp @@ -0,0 +1,136 @@ +/** + * @file + * @brief Unit tests for openshot::Profile + * @author Jonathan Thomas + * + * @ref License + */ + +// Copyright (c) 2008-2023 OpenShot Studios, LLC +// +// SPDX-License-Identifier: LGPL-3.0-or-later + +#include "openshot_catch.h" + + +#include "Profiles.h" + +TEST_CASE( "empty constructor", "[libopenshot][profile]" ) +{ + openshot::Profile p1; + + // Default values + CHECK(p1.info.width == 0); + CHECK(p1.info.height == 0); + CHECK(p1.info.fps.num == 0); + CHECK(p1.info.fps.den == 0); + CHECK(p1.info.display_ratio.num == 0); + CHECK(p1.info.display_ratio.den == 0); + CHECK(p1.info.pixel_ratio.num == 0); + CHECK(p1.info.pixel_ratio.den == 0); + CHECK(p1.info.interlaced_frame == false); + +} + +TEST_CASE( "constructor with example profiles", "[libopenshot][profile]" ) +{ + std::stringstream profile1; + profile1 << TEST_MEDIA_PATH << "example_profile1"; + + openshot::Profile p1(profile1.str()); + + // Default values + CHECK(p1.info.width == 1280); + CHECK(p1.info.height == 720); + CHECK(p1.info.fps.num == 24); + CHECK(p1.info.fps.den == 1); + CHECK(p1.info.display_ratio.num == 16); + CHECK(p1.info.display_ratio.den == 9); + CHECK(p1.info.pixel_ratio.num == 1); + CHECK(p1.info.pixel_ratio.den == 1); + CHECK(p1.info.interlaced_frame == false); + + std::stringstream profile2; + profile2 << TEST_MEDIA_PATH << "example_profile2"; + + openshot::Profile p2(profile2.str()); + + // Default values + CHECK(p2.info.width == 1920); + CHECK(p2.info.height == 1080); + CHECK(p2.info.fps.num == 30000); + CHECK(p2.info.fps.den == 1001); + CHECK(p2.info.display_ratio.num == 16); + CHECK(p2.info.display_ratio.den == 9); + CHECK(p2.info.pixel_ratio.num == 1); + CHECK(p2.info.pixel_ratio.den == 1); + CHECK(p2.info.interlaced_frame == true); +} + +TEST_CASE( "24 fps names", "[libopenshot][profile]" ) +{ + std::stringstream path; + path << TEST_MEDIA_PATH << "example_profile1"; + + openshot::Profile p(path.str()); + + // Default values + CHECK(p.Key() == "01280x0720p0024_16-09"); + CHECK(p.ShortName() == "1280x720p24"); + CHECK(p.LongName() == "1280x720p @ 24 fps (16:9)"); + CHECK(p.LongNameWithDesc() == "1280x720p @ 24 fps (16:9) HD 720p 24 fps"); +} + +TEST_CASE( "29.97 fps names", "[libopenshot][profile]" ) +{ + std::stringstream path; + path << TEST_MEDIA_PATH << "example_profile2"; + + openshot::Profile p(path.str()); + + // Default values + CHECK(p.Key() == "01920x1080i2997_16-09"); + CHECK(p.ShortName() == "1920x1080i29.97"); + CHECK(p.LongName() == "1920x1080i @ 29.97 fps (16:9)"); + CHECK(p.LongNameWithDesc() == "1920x1080i @ 29.97 fps (16:9) HD 1080i 29.97 fps"); +} + +TEST_CASE( "compare profiles", "[libopenshot][profile]" ) +{ + // 720p24 + std::stringstream profile1; + profile1 << TEST_MEDIA_PATH << "example_profile1"; + openshot::Profile p1(profile1.str()); + + // 720p24 (copy) + openshot::Profile p1copy(profile1.str()); + + // 1080i2997 + std::stringstream profile2; + profile2 << TEST_MEDIA_PATH << "example_profile2"; + openshot::Profile p2(profile2.str()); + + // 1080i2997 (copy) + openshot::Profile p2copy(profile2.str()); + + CHECK(p1 < p2); + CHECK(p2 > p1); + CHECK(p1 == p1copy); + CHECK(p2 == p2copy); + + // 720p60 + openshot::Profile p3(profile1.str()); + p3.info.fps.num = 60; + + CHECK(p1 < p3); + CHECK_FALSE(p1 == p3); + + // 72024, DAR: 4:3 + p3.info.fps.num = 24; + p3.info.display_ratio.num = 4; + p3.info.display_ratio.den = 3; + + CHECK(p1 > p3); + CHECK(p3 < p1); + CHECK_FALSE(p1 == p3); +}