From 70e86ef044f74ff734525badeefa44e027916945 Mon Sep 17 00:00:00 2001 From: Jonathan Thomas Date: Thu, 2 Feb 2023 16:29:38 -0600 Subject: [PATCH] Improved Profile Class (Helper methods, Sortable, Unit tests) (#895) * Removing legacy profile property. Add new operators for Profile classes (for comparison). Also added new functions to generate different variations of the Profile data (key, short name, long name, long name w/description). * Add empty constructor for Profile class, and new Profile unit tets * Adding zero padding to profile Key function, for easier sorting: 01920x1080i2997_16:09 * Clear setfill flag after creating Key() output * Updating example exe to load an *.osp project file via C++, which makes debugging complex broken projects much easier. * - Add new unit test to FFmpegWriter to create an animated GIF and verify it can be wrapped with a FrameMapper (with no audio track) - Improve FrameMapper to ignore missing audio data (i.e. when no audio samples present, don't try and find them or resample them) * Fix some whitespace issues * Fix inline documentation mistype * Fixed missing reuse licensing on new example profile files * Changing Profile::Key() format to exclude the : character, since Windows file names cannot contain that * - Large memory leak fixed in FFmpegWriter when closing the video & audio contexts - Reducing # of cached frames and rescalers to 1, since we no longer use OMP and this is unneeded - we need to refactor much of this code out eventually * - Fixing whitespace issues - Code clean-up / line wrapping / etc... --- .reuse/dep5 | 2 +- examples/Example.cpp | 49 +- examples/example_profile1 | 11 + examples/example_profile2 | 11 + src/FFmpegWriter.cpp | 1103 +++++++++++++++++++------------------ src/FrameMapper.cpp | 159 +++--- src/Profiles.cpp | 112 +++- src/Profiles.h | 77 +++ tests/CMakeLists.txt | 1 + tests/FFmpegWriter.cpp | 50 ++ tests/Profiles.cpp | 136 +++++ 11 files changed, 1064 insertions(+), 647 deletions(-) create mode 100644 examples/example_profile1 create mode 100644 examples/example_profile2 create mode 100644 tests/Profiles.cpp 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); +}