From ee31da9e71cf47cfd853d98c1e59390f6b1b7f02 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Sat, 21 Dec 2019 20:03:25 +1100 Subject: [PATCH] merged encoder and decoder code into one folder since they can share quite a bit of code --- app/CMakeLists.txt | 3 +- app/{decoder => codec}/CMakeLists.txt | 18 +- app/{decoder => codec}/decoder.cpp | 4 +- app/{decoder => codec}/decoder.h | 2 +- app/codec/encoder.cpp | 79 ++++ app/codec/encoder.h | 74 ++++ app/{decoder => codec}/ffmpeg/CMakeLists.txt | 8 +- app/codec/ffmpeg/ffmpegcommon.cpp | 101 +++++ app/codec/ffmpeg/ffmpegcommon.h | 39 ++ .../ffmpeg/ffmpegdecoder.cpp | 81 +--- app/{decoder => codec}/ffmpeg/ffmpegdecoder.h | 13 +- app/codec/ffmpeg/ffmpegencoder.cpp | 357 ++++++++++++++++++ app/codec/ffmpeg/ffmpegencoder.h | 60 +++ app/{decoder => codec}/frame.cpp | 3 +- app/{decoder => codec}/frame.h | 2 - app/{decoder => codec}/oiio/CMakeLists.txt | 4 +- app/{decoder => codec}/oiio/oiiodecoder.cpp | 0 app/{decoder => codec}/oiio/oiiodecoder.h | 2 +- app/{decoder => codec}/waveinput.cpp | 0 app/{decoder => codec}/waveinput.h | 0 app/{decoder => codec}/waveoutput.cpp | 0 app/{decoder => codec}/waveoutput.h | 0 app/encoder/CMakeLists.txt | 24 -- app/encoder/encoder.cpp | 31 -- app/encoder/encoder.h | 49 --- 25 files changed, 744 insertions(+), 210 deletions(-) rename app/{decoder => codec}/CMakeLists.txt (80%) rename app/{decoder => codec}/decoder.cpp (97%) rename app/{decoder => codec}/decoder.h (99%) create mode 100644 app/codec/encoder.cpp create mode 100644 app/codec/encoder.h rename app/{decoder => codec}/ffmpeg/CMakeLists.txt (79%) create mode 100644 app/codec/ffmpeg/ffmpegcommon.cpp create mode 100644 app/codec/ffmpeg/ffmpegcommon.h rename app/{decoder => codec}/ffmpeg/ffmpegdecoder.cpp (92%) rename app/{decoder => codec}/ffmpeg/ffmpegdecoder.h (92%) create mode 100644 app/codec/ffmpeg/ffmpegencoder.cpp create mode 100644 app/codec/ffmpeg/ffmpegencoder.h rename app/{decoder => codec}/frame.cpp (98%) rename app/{decoder => codec}/frame.h (99%) rename app/{decoder => codec}/oiio/CMakeLists.txt (92%) rename app/{decoder => codec}/oiio/oiiodecoder.cpp (100%) rename app/{decoder => codec}/oiio/oiiodecoder.h (98%) rename app/{decoder => codec}/waveinput.cpp (100%) rename app/{decoder => codec}/waveinput.h (100%) rename app/{decoder => codec}/waveoutput.cpp (100%) rename app/{decoder => codec}/waveoutput.h (100%) delete mode 100644 app/encoder/CMakeLists.txt delete mode 100644 app/encoder/encoder.cpp delete mode 100644 app/encoder/encoder.h diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index 63a0ac9db..46af301e6 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -26,11 +26,10 @@ set(OLIVE_RESOURCES ) add_subdirectory(audio) +add_subdirectory(codec) add_subdirectory(common) add_subdirectory(config) -add_subdirectory(decoder) add_subdirectory(dialog) -add_subdirectory(encoder) add_subdirectory(node) add_subdirectory(panel) add_subdirectory(project) diff --git a/app/decoder/CMakeLists.txt b/app/codec/CMakeLists.txt similarity index 80% rename from app/decoder/CMakeLists.txt rename to app/codec/CMakeLists.txt index 8d0f1cbd4..6a1158a72 100644 --- a/app/decoder/CMakeLists.txt +++ b/app/codec/CMakeLists.txt @@ -19,13 +19,15 @@ add_subdirectory(oiio) set(OLIVE_SOURCES ${OLIVE_SOURCES} - decoder/decoder.h - decoder/decoder.cpp - decoder/frame.h - decoder/frame.cpp - decoder/waveinput.h - decoder/waveinput.cpp - decoder/waveoutput.h - decoder/waveoutput.cpp + codec/decoder.h + codec/decoder.cpp + codec/encoder.h + codec/encoder.cpp + codec/frame.h + codec/frame.cpp + codec/waveinput.h + codec/waveinput.cpp + codec/waveoutput.h + codec/waveoutput.cpp PARENT_SCOPE ) diff --git a/app/decoder/decoder.cpp b/app/codec/decoder.cpp similarity index 97% rename from app/decoder/decoder.cpp rename to app/codec/decoder.cpp index 1659614d9..6a243fc45 100644 --- a/app/decoder/decoder.cpp +++ b/app/codec/decoder.cpp @@ -24,8 +24,8 @@ #include #include -#include "decoder/ffmpeg/ffmpegdecoder.h" -#include "decoder/oiio/oiiodecoder.h" +#include "codec/ffmpeg/ffmpegdecoder.h" +#include "codec/oiio/oiiodecoder.h" Decoder::Decoder() : open_(false), diff --git a/app/decoder/decoder.h b/app/codec/decoder.h similarity index 99% rename from app/decoder/decoder.h rename to app/codec/decoder.h index 53fbc5026..3bdc5492d 100644 --- a/app/decoder/decoder.h +++ b/app/codec/decoder.h @@ -24,10 +24,10 @@ #include #include +#include "codec/frame.h" #include "common/constructors.h" #include "common/rational.h" #include "project/item/footage/footage.h" -#include "decoder/frame.h" class Decoder; using DecoderPtr = std::shared_ptr; diff --git a/app/codec/encoder.cpp b/app/codec/encoder.cpp new file mode 100644 index 000000000..0304e75db --- /dev/null +++ b/app/codec/encoder.cpp @@ -0,0 +1,79 @@ +#include "encoder.h" + +#include "ffmpeg/ffmpegencoder.h" + +Encoder::Encoder(const EncodingParams ¶ms) : + open_(false), + params_(params) +{ +} + +const EncodingParams &Encoder::params() const +{ + return params_; +} + +EncodingParams::EncodingParams() : + video_enabled_(false), + audio_enabled_(false) +{ +} + +void EncodingParams::SetFilename(const QString &filename) +{ + filename_ = filename; +} + +void EncodingParams::EnableVideo(const VideoRenderingParams &video_params, const QString &vcodec) +{ + video_enabled_ = true; + video_params_ = video_params; + video_codec_ = vcodec; +} + +void EncodingParams::EnableAudio(const AudioRenderingParams &audio_params, const QString &acodec) +{ + audio_enabled_ = true; + audio_params_ = audio_params; + audio_codec_ = acodec; +} + +const QString &EncodingParams::filename() const +{ + return filename_; +} + +bool EncodingParams::video_enabled() const +{ + return video_enabled_; +} + +const QString &EncodingParams::video_codec() const +{ + return video_codec_; +} + +const VideoRenderingParams &EncodingParams::video_params() const +{ + return video_params_; +} + +bool EncodingParams::audio_enabled() const +{ + return audio_enabled_; +} + +const QString &EncodingParams::audio_codec() const +{ + return audio_codec_; +} + +const AudioRenderingParams &EncodingParams::audio_params() const +{ + return audio_params_; +} + +EncoderPtr Encoder::CreateFromID(const QString &id, const EncodingParams& params) +{ + return std::make_shared(params); +} diff --git a/app/codec/encoder.h b/app/codec/encoder.h new file mode 100644 index 000000000..3a8afe988 --- /dev/null +++ b/app/codec/encoder.h @@ -0,0 +1,74 @@ +#ifndef ENCODER_H +#define ENCODER_H + +#include +#include + +#include "codec/frame.h" +#include "common/constructors.h" +#include "render/audioparams.h" +#include "render/videoparams.h" + +class Encoder; +using EncoderPtr = std::shared_ptr; + +class EncodingParams { +public: + EncodingParams(); + + void SetFilename(const QString& filename); + void EnableVideo(const VideoRenderingParams& video_params, const QString& vcodec); + void EnableAudio(const AudioRenderingParams& audio_params, const QString& acodec); + + const QString& filename() const; + + bool video_enabled() const; + const QString& video_codec() const; + const VideoRenderingParams& video_params() const; + + bool audio_enabled() const; + const QString& audio_codec() const; + const AudioRenderingParams& audio_params() const; + +private: + QString filename_; + + bool video_enabled_; + QString video_codec_; + VideoRenderingParams video_params_; + + bool audio_enabled_; + QString audio_codec_; + AudioRenderingParams audio_params_; +}; + +class Encoder +{ +public: + Encoder(const EncodingParams& params); + + DISABLE_COPY_MOVE(Encoder) + + virtual bool Open() = 0; + virtual void Write(FramePtr frame) = 0; + virtual void Close() = 0; + + /** + * @brief Create a Encoder instance using a Encoder ID + * + * @return + * + * A Encoder instance or nullptr if a Decoder with this ID does not exist + */ + static EncoderPtr CreateFromID(const QString& id, const EncodingParams ¶ms); + +protected: + const EncodingParams& params() const; + + bool open_; + +private: + EncodingParams params_; +}; + +#endif // ENCODER_H diff --git a/app/decoder/ffmpeg/CMakeLists.txt b/app/codec/ffmpeg/CMakeLists.txt similarity index 79% rename from app/decoder/ffmpeg/CMakeLists.txt rename to app/codec/ffmpeg/CMakeLists.txt index 27512870b..3d75f8a2c 100644 --- a/app/decoder/ffmpeg/CMakeLists.txt +++ b/app/codec/ffmpeg/CMakeLists.txt @@ -16,7 +16,11 @@ set(OLIVE_SOURCES ${OLIVE_SOURCES} - decoder/ffmpeg/ffmpegdecoder.h - decoder/ffmpeg/ffmpegdecoder.cpp + codec/ffmpeg/ffmpegcommon.h + codec/ffmpeg/ffmpegcommon.cpp + codec/ffmpeg/ffmpegdecoder.h + codec/ffmpeg/ffmpegdecoder.cpp + codec/ffmpeg/ffmpegencoder.h + codec/ffmpeg/ffmpegencoder.cpp PARENT_SCOPE ) diff --git a/app/codec/ffmpeg/ffmpegcommon.cpp b/app/codec/ffmpeg/ffmpegcommon.cpp new file mode 100644 index 000000000..82337eb78 --- /dev/null +++ b/app/codec/ffmpeg/ffmpegcommon.cpp @@ -0,0 +1,101 @@ +#include "ffmpegcommon.h" + +AVPixelFormat FFmpegCommon::GetCompatiblePixelFormat(const AVPixelFormat &pix_fmt) +{ + AVPixelFormat possible_pix_fmts[] = { + AV_PIX_FMT_RGBA, + AV_PIX_FMT_RGBA64, + AV_PIX_FMT_NONE + }; + + return avcodec_find_best_pix_fmt_of_list(possible_pix_fmts, + pix_fmt, + 1, + nullptr); +} + +SampleFormat FFmpegCommon::GetNativeSampleFormat(const AVSampleFormat &smp_fmt) +{ + switch (smp_fmt) { + case AV_SAMPLE_FMT_U8: + return SAMPLE_FMT_U8; + case AV_SAMPLE_FMT_S16: + return SAMPLE_FMT_S16; + case AV_SAMPLE_FMT_S32: + return SAMPLE_FMT_S32; + case AV_SAMPLE_FMT_S64: + return SAMPLE_FMT_S64; + case AV_SAMPLE_FMT_FLT: + return SAMPLE_FMT_FLT; + case AV_SAMPLE_FMT_DBL: + return SAMPLE_FMT_DBL; + case AV_SAMPLE_FMT_U8P : + case AV_SAMPLE_FMT_S16P: + case AV_SAMPLE_FMT_S32P: + case AV_SAMPLE_FMT_S64P: + case AV_SAMPLE_FMT_FLTP: + case AV_SAMPLE_FMT_DBLP: + case AV_SAMPLE_FMT_NONE: + case AV_SAMPLE_FMT_NB: + break; + } + + return SAMPLE_FMT_INVALID; +} + +AVSampleFormat FFmpegCommon::GetFFmpegSampleFormat(const SampleFormat &smp_fmt) +{ + switch (smp_fmt) { + case SAMPLE_FMT_U8: + return AV_SAMPLE_FMT_U8; + case SAMPLE_FMT_S16: + return AV_SAMPLE_FMT_S16; + case SAMPLE_FMT_S32: + return AV_SAMPLE_FMT_S32; + case SAMPLE_FMT_S64: + return AV_SAMPLE_FMT_S64; + case SAMPLE_FMT_FLT: + return AV_SAMPLE_FMT_FLT; + case SAMPLE_FMT_DBL: + return AV_SAMPLE_FMT_DBL; + case SAMPLE_FMT_INVALID: + case SAMPLE_FMT_COUNT: + break; + } + + return AV_SAMPLE_FMT_NONE; +} + +AVPixelFormat FFmpegCommon::GetFFmpegPixelFormat(const olive::PixelFormat &pix_fmt) +{ + switch (pix_fmt) { + case olive::PIX_FMT_RGBA8: + return AV_PIX_FMT_RGBA; + case olive::PIX_FMT_RGBA16U: + return AV_PIX_FMT_RGBA64; + case olive::PIX_FMT_RGBA16F: + case olive::PIX_FMT_RGBA32F: + case olive::PIX_FMT_INVALID: + case olive::PIX_FMT_COUNT: + break; + } + + return AV_PIX_FMT_NONE; +} + +olive::PixelFormat FFmpegCommon::GetCompatiblePixelFormat(const olive::PixelFormat &pix_fmt) +{ + switch (pix_fmt) { + case olive::PIX_FMT_RGBA8: + return olive::PIX_FMT_RGBA8; + case olive::PIX_FMT_RGBA16U: + case olive::PIX_FMT_RGBA16F: + case olive::PIX_FMT_RGBA32F: + return olive::PIX_FMT_RGBA16U; + case olive::PIX_FMT_INVALID: + case olive::PIX_FMT_COUNT: + break; + } + + return olive::PIX_FMT_INVALID; +} diff --git a/app/codec/ffmpeg/ffmpegcommon.h b/app/codec/ffmpeg/ffmpegcommon.h new file mode 100644 index 000000000..f04d87c8a --- /dev/null +++ b/app/codec/ffmpeg/ffmpegcommon.h @@ -0,0 +1,39 @@ +#ifndef FFMPEGABSTRACTION_H +#define FFMPEGABSTRACTION_H + +extern "C" { +#include +} + +#include "audio/sampleformat.h" +#include "render/pixelformat.h" + +class FFmpegCommon { +public: + /** + * @brief Returns an AVPixelFormat that can be used to convert a frame to a data type Olive supports with minimal data loss + */ + static AVPixelFormat GetCompatiblePixelFormat(const AVPixelFormat& pix_fmt); + + /** + * @brief Returns a native pixel format that can be used to convert from a native frame to an AVFrame with minimal data loss + */ + static olive::PixelFormat GetCompatiblePixelFormat(const olive::PixelFormat& pix_fmt); + + /** + * @brief Returns an FFmpeg pixel format for a given native pixel format + */ + static AVPixelFormat GetFFmpegPixelFormat(const olive::PixelFormat& pix_fmt); + + /** + * @brief Returns a native sample format type for a given AVSampleFormat + */ + static SampleFormat GetNativeSampleFormat(const AVSampleFormat& smp_fmt); + + /** + * @brief Returns an FFmpeg sample format type for a given native type + */ + static AVSampleFormat GetFFmpegSampleFormat(const SampleFormat& smp_fmt); +}; + +#endif // FFMPEGABSTRACTION_H diff --git a/app/decoder/ffmpeg/ffmpegdecoder.cpp b/app/codec/ffmpeg/ffmpegdecoder.cpp similarity index 92% rename from app/decoder/ffmpeg/ffmpegdecoder.cpp rename to app/codec/ffmpeg/ffmpegdecoder.cpp index d84fad177..889450ada 100644 --- a/app/decoder/ffmpeg/ffmpegdecoder.cpp +++ b/app/codec/ffmpeg/ffmpegdecoder.cpp @@ -32,9 +32,10 @@ extern "C" { #include #include +#include "codec/waveinput.h" #include "common/filefunctions.h" #include "common/timecodefunctions.h" -#include "decoder/waveinput.h" +#include "ffmpegcommon.h" #include "render/pixelservice.h" FFmpegDecoder::FFmpegDecoder() : @@ -58,7 +59,7 @@ bool FFmpegDecoder::Open() int error_code; - // Convert QString to a C strng + // Convert QString to a C string QByteArray ba = stream()->footage()->filename().toUtf8(); const char* filename = ba.constData(); @@ -134,7 +135,7 @@ bool FFmpegDecoder::Open() AVPixelFormat pix_fmt = static_cast(avstream_->codecpar->format); // Get an Olive compatible AVPixelFormat - AVPixelFormat ideal_pix_fmt = GetCompatiblePixelFormat(pix_fmt); + AVPixelFormat ideal_pix_fmt = FFmpegCommon::GetCompatiblePixelFormat(pix_fmt); // Determine which Olive native pixel format we retrieved // Note that FFmpeg doesn't support float formats @@ -224,7 +225,7 @@ FramePtr FFmpegDecoder::RetrieveVideo(const rational &timecode) frame_container->set_width(frame->width); frame_container->set_height(frame->height); frame_container->set_format(static_cast(output_fmt_)); - frame_container->set_timestamp(olive::timestamp_to_time(frame->pts, avstream_->time_base)); + frame_container->set_timestamp(olive::timestamp_to_time(target_ts, avstream_->time_base)); frame_container->allocate(); // Convert pixel format/linesize if necessary @@ -370,10 +371,10 @@ void FFmpegDecoder::Conform(const AudioRenderingParams ¶ms) // Set up resampler SwrContext* resampler = swr_alloc_set_opts(nullptr, static_cast(params.channel_layout()), - GetFFmpegSampleFormat(params.format()), + FFmpegCommon::GetFFmpegSampleFormat(params.format()), params.sample_rate(), static_cast(input.params().channel_layout()), - GetFFmpegSampleFormat(input.params().format()), + FFmpegCommon::GetFFmpegSampleFormat(input.params().format()), input.params().sample_rate(), 0, nullptr); @@ -765,7 +766,7 @@ void FFmpegDecoder::IndexAudio(AVPacket *pkt, AVFrame *frame) WaveOutput wave_out(GetIndexFilename(), AudioRenderingParams(avstream_->codecpar->sample_rate, channel_layout, - GetNativeSampleFormat(dst_sample_fmt))); + FFmpegCommon::GetNativeSampleFormat(dst_sample_fmt))); int ret; @@ -913,72 +914,6 @@ int FFmpegDecoder::GetFrame(AVPacket *pkt, AVFrame *frame) return ret; } -AVPixelFormat FFmpegDecoder::GetCompatiblePixelFormat(const AVPixelFormat &pix_fmt) -{ - AVPixelFormat possible_pix_fmts[] = { - AV_PIX_FMT_RGBA, - AV_PIX_FMT_RGBA64, - AV_PIX_FMT_NONE - }; - - return avcodec_find_best_pix_fmt_of_list(possible_pix_fmts, - pix_fmt, - 1, - nullptr); -} - -SampleFormat FFmpegDecoder::GetNativeSampleFormat(const AVSampleFormat &smp_fmt) -{ - switch (smp_fmt) { - case AV_SAMPLE_FMT_U8: - return SAMPLE_FMT_U8; - case AV_SAMPLE_FMT_S16: - return SAMPLE_FMT_S16; - case AV_SAMPLE_FMT_S32: - return SAMPLE_FMT_S32; - case AV_SAMPLE_FMT_S64: - return SAMPLE_FMT_S64; - case AV_SAMPLE_FMT_FLT: - return SAMPLE_FMT_FLT; - case AV_SAMPLE_FMT_DBL: - return SAMPLE_FMT_DBL; - case AV_SAMPLE_FMT_U8P : - case AV_SAMPLE_FMT_S16P: - case AV_SAMPLE_FMT_S32P: - case AV_SAMPLE_FMT_S64P: - case AV_SAMPLE_FMT_FLTP: - case AV_SAMPLE_FMT_DBLP: - case AV_SAMPLE_FMT_NONE: - case AV_SAMPLE_FMT_NB: - break; - } - - return SAMPLE_FMT_INVALID; -} - -AVSampleFormat FFmpegDecoder::GetFFmpegSampleFormat(const SampleFormat &smp_fmt) -{ - switch (smp_fmt) { - case SAMPLE_FMT_U8: - return AV_SAMPLE_FMT_U8; - case SAMPLE_FMT_S16: - return AV_SAMPLE_FMT_S16; - case SAMPLE_FMT_S32: - return AV_SAMPLE_FMT_S32; - case SAMPLE_FMT_S64: - return AV_SAMPLE_FMT_S64; - case SAMPLE_FMT_FLT: - return AV_SAMPLE_FMT_FLT; - case SAMPLE_FMT_DBL: - return AV_SAMPLE_FMT_DBL; - case SAMPLE_FMT_INVALID: - case SAMPLE_FMT_COUNT: - break; - } - - return AV_SAMPLE_FMT_NONE; -} - int FFmpegDecoder::CalculatePlaneHeight(int frame_height, const AVPixelFormat &format, int plane) { // FIXME: This seems dumb, but I can't find any FFmpeg function that returns this information diff --git a/app/decoder/ffmpeg/ffmpegdecoder.h b/app/codec/ffmpeg/ffmpegdecoder.h similarity index 92% rename from app/decoder/ffmpeg/ffmpegdecoder.h rename to app/codec/ffmpeg/ffmpegdecoder.h index f3604d97e..9ca114ec5 100644 --- a/app/decoder/ffmpeg/ffmpegdecoder.h +++ b/app/codec/ffmpeg/ffmpegdecoder.h @@ -30,8 +30,8 @@ extern "C" { #include #include "audio/sampleformat.h" -#include "decoder/decoder.h" -#include "decoder/waveoutput.h" +#include "codec/decoder.h" +#include "codec/waveoutput.h" /** * @brief A Decoder derivative that wraps FFmpeg functions as on Olive decoder @@ -139,15 +139,6 @@ private: void Seek(int64_t timestamp); - /** - * @brief Returns an AVPixelFormat that can be used in Olive and causes minimal data loss - */ - AVPixelFormat GetCompatiblePixelFormat(const AVPixelFormat& pix_fmt); - - SampleFormat GetNativeSampleFormat(const AVSampleFormat& smp_fmt); - - AVSampleFormat GetFFmpegSampleFormat(const SampleFormat& smp_fmt); - int CalculatePlaneHeight(int frame_height, const AVPixelFormat& format, int plane); AVFormatContext* fmt_ctx_; diff --git a/app/codec/ffmpeg/ffmpegencoder.cpp b/app/codec/ffmpeg/ffmpegencoder.cpp new file mode 100644 index 000000000..9690ecce4 --- /dev/null +++ b/app/codec/ffmpeg/ffmpegencoder.cpp @@ -0,0 +1,357 @@ +#include "ffmpegencoder.h" + +#include "ffmpegcommon.h" +#include "render/pixelservice.h" + +FFmpegEncoder::FFmpegEncoder(const EncodingParams ¶ms) : + Encoder(params), + fmt_ctx_(nullptr), + video_stream_(nullptr), + video_codec_ctx_(nullptr), + video_scale_ctx_(nullptr), + audio_stream_(nullptr), + audio_codec_ctx_(nullptr), + audio_resample_ctx_(nullptr) +{ +} + +bool FFmpegEncoder::Open() +{ + if (open_) { + return true; + } + + int error_code; + + // Convert QString to C string that FFmpeg expects + QByteArray filename_bytes = params().filename().toUtf8(); + const char* filename_c_str = filename_bytes.constData(); + + // Create output format context + error_code = avformat_alloc_output_context2(&fmt_ctx_, nullptr, nullptr, filename_c_str); + + // Check error code + if (error_code < 0) { + FFmpegError(error_code); + return false; + } + + // Initialize a video stream if it's enabled + if (params().video_enabled()) { + if (!InitializeStream(AVMEDIA_TYPE_VIDEO, &video_stream_, &video_codec_ctx_, params().video_codec())) { + return false; + } + + + + // This is the format we will expect frames received in Write() to be in + olive::PixelFormat native_pixel_fmt = params().video_params().format(); + + // This is the format we will need to convert the frame to for swscale to understand it + video_conversion_fmt_ = FFmpegCommon::GetCompatiblePixelFormat(native_pixel_fmt); + + // This is the equivalent pixel format above as an AVPixelFormat that swscale can understand + AVPixelFormat src_pix_fmt = FFmpegCommon::GetFFmpegPixelFormat(video_conversion_fmt_); + + // This is the pixel format the encoder wants to encode to + AVPixelFormat encoder_pix_fmt = video_codec_ctx_->pix_fmt; + + // Set up a scaling context - if the native pixel format is not equal to the encoder's, we'll need to convert it + // before encoding. Even if we don't, this may be useful for converting between linesizes, etc. + video_scale_ctx_ = sws_getContext(params().video_params().width(), + params().video_params().height(), + src_pix_fmt, + params().video_params().width(), + params().video_params().height(), + encoder_pix_fmt, + 0, + nullptr, + nullptr, + nullptr); + } + + // Initialize an audio stream if it's enabled + if (params().audio_enabled() + && !InitializeStream(AVMEDIA_TYPE_AUDIO, &audio_stream_, &audio_codec_ctx_, params().audio_codec())) { + return false; + } + + av_dump_format(fmt_ctx_, 0, filename_c_str, 1); + + // Open output file for writing + error_code = avio_open(&fmt_ctx_->pb, filename_c_str, AVIO_FLAG_WRITE); + if (error_code < 0) { + FFmpegError(error_code); + return false; + } + + // Write header + error_code = avformat_write_header(fmt_ctx_, nullptr); + if (error_code < 0) { + FFmpegError(error_code); + return false; + } + + open_ = true; + + return true; +} + +void FFmpegEncoder::Write(FramePtr frame) +{ + AVFrame* encoded_frame = av_frame_alloc(); + AVPacket* pkt = av_packet_alloc(); + + int error_code; + + AVCodecContext* codec_ctx; + AVStream* stream; + + if (frame->width() > 0) { + // Frame must be video + encoded_frame->width = frame->width(); + encoded_frame->height = frame->height(); + encoded_frame->format = video_codec_ctx_->pix_fmt; + + error_code = av_frame_get_buffer(encoded_frame, 0); + if (error_code < 0) { + FFmpegError(error_code); + goto fail; + } + + // We may need to convert this frame to a frame that swscale will understand + if (frame->format() != video_conversion_fmt_) { + frame = PixelService::ConvertPixelFormat(frame, video_conversion_fmt_); + } + + // Use swscale context to convert formats/linesizes + const char* input_data = frame->const_data(); + int input_linesize = frame->width() * PixelService::BytesPerPixel(video_conversion_fmt_); + error_code = sws_scale(video_scale_ctx_, + reinterpret_cast(&input_data), + &input_linesize, + 0, + frame->height(), + encoded_frame->data, + encoded_frame->linesize); + if (error_code < 0) { + goto fail; + } + + codec_ctx = video_codec_ctx_; + stream = video_stream_; + } else { + // Frame must be audio + codec_ctx = audio_codec_ctx_; + stream = audio_stream_; + } + + encoded_frame->pts = qRound(frame->timestamp().toDouble() / av_q2d(codec_ctx->time_base)); + + // Send raw frame to the encoder + error_code = avcodec_send_frame(codec_ctx, encoded_frame); + if (error_code < 0) { + FFmpegError(error_code); + goto fail; + } + + // Retrieve packets from encoder + while (error_code >= 0) { + error_code = avcodec_receive_packet(codec_ctx, pkt); + + // EAGAIN just means the encoder wants another frame before encoding + if (error_code == AVERROR(EAGAIN)) { + break; + } else if (error_code < 0) { + FFmpegError(error_code); + goto fail; + } + + // Set packet stream index + pkt->stream_index = stream->index; + + av_packet_rescale_ts(pkt, codec_ctx->time_base, stream->time_base); + + // Write packet to file + av_interleaved_write_frame(fmt_ctx_, pkt); + + // Unref packet in case we're getting another + av_packet_unref(pkt); + } + +fail: + av_packet_free(&pkt); + av_frame_free(&encoded_frame); +} + +void FFmpegEncoder::Close() +{ + if (open_) { + // Flush encoders + FlushEncoders(); + + // We've written a header, so we'll write a trailer + av_write_trailer(fmt_ctx_); + avio_closep(&fmt_ctx_->pb); + } + + if (video_scale_ctx_) { + sws_freeContext(video_scale_ctx_); + video_scale_ctx_ = nullptr; + } + + if (video_codec_ctx_) { + avcodec_free_context(&video_codec_ctx_); + video_codec_ctx_ = nullptr; + } + + if (audio_codec_ctx_) { + avcodec_free_context(&audio_codec_ctx_); + audio_codec_ctx_ = nullptr; + } + + if (fmt_ctx_) { + // NOTE: This also frees video_stream_ and audio_stream_ + avformat_free_context(fmt_ctx_); + fmt_ctx_ = nullptr; + } +} + +void FFmpegEncoder::FFmpegError(int error_code) +{ + char err[1024]; + av_strerror(error_code, err, 1024); + + Error(QStringLiteral("Error encoding %1 - %2 %3").arg(params().filename(), + QString::number(error_code), + err)); +} + +bool FFmpegEncoder::InitializeStream(AVMediaType type, AVStream** stream_ptr, AVCodecContext** codec_ctx_ptr, const QString& codec) +{ + if (type != AVMEDIA_TYPE_VIDEO && type != AVMEDIA_TYPE_AUDIO) { + Error(QStringLiteral("Cannot initialize a stream that is not a video or audio type")); + return false; + } + + // Retrieve codec and convert to C string + QByteArray codec_bytes = codec.toUtf8(); + const char* codec_c_str = codec_bytes.constData(); + + // Find encoder with this name + AVCodec* encoder = avcodec_find_encoder_by_name(codec_c_str); + + if (!encoder) { + Error(QStringLiteral("Failed to find codec for %1").arg(codec)); + return false; + } + + if (encoder->type != type) { + Error(QStringLiteral("Retrieved unexpected codec type %1 for codec %2").arg(QString::number(encoder->type), codec)); + return false; + } + + if (!InitializeCodecContext(stream_ptr, codec_ctx_ptr, encoder)) { + return false; + } + + // Set codec parameters + AVCodecContext* codec_ctx = *codec_ctx_ptr; + AVStream* stream = *stream_ptr; + + if (type == AVMEDIA_TYPE_VIDEO) { + codec_ctx->width = params().video_params().width(); + codec_ctx->height = params().video_params().height(); + codec_ctx->sample_aspect_ratio = {1, 1}; + codec_ctx->time_base = params().video_params().time_base().toAVRational(); + + // FIXME: Make this customizable again + codec_ctx->pix_fmt = encoder->pix_fmts[0]; + } else { + codec_ctx->sample_rate = params().audio_params().sample_rate(); + codec_ctx->channel_layout = params().audio_params().channel_layout(); + codec_ctx->channels = av_get_channel_layout_nb_channels(codec_ctx->channel_layout); + codec_ctx->sample_fmt = encoder->sample_fmts[0]; + codec_ctx->time_base = {1, codec_ctx->sample_rate}; + } + + if (!SetupCodecContext(stream, codec_ctx, encoder)) { + return false; + } + + return true; +} + +bool FFmpegEncoder::InitializeCodecContext(AVStream **stream, AVCodecContext **codec_ctx, AVCodec* codec) +{ + *stream = avformat_new_stream(fmt_ctx_, nullptr); + if (!(*stream)) { + Error(QStringLiteral("Failed to allocate AVStream")); + return false; + } + + // Allocate a codec context + *codec_ctx = avcodec_alloc_context3(codec); + if (!(*codec_ctx)) { + Error(QStringLiteral("Failed to allocate AVCodecContext")); + return false; + } + + return true; +} + +bool FFmpegEncoder::SetupCodecContext(AVStream* stream, AVCodecContext* codec_ctx, AVCodec* codec) +{ + int error_code; + + if (fmt_ctx_->oformat->flags & AVFMT_GLOBALHEADER) { + codec_ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; + } + + // Try to open encoder + error_code = avcodec_open2(codec_ctx, codec, nullptr); + if (error_code < 0) { + FFmpegError(error_code); + return false; + } + + // Copy context settings to codecpar object + error_code = avcodec_parameters_from_context(stream->codecpar, codec_ctx); + if (error_code < 0) { + FFmpegError(error_code); + return false; + } + + return true; +} + +void FFmpegEncoder::FlushEncoders() +{ + if (video_codec_ctx_) { + avcodec_send_frame(video_codec_ctx_, nullptr); + AVPacket* pkt = av_packet_alloc(); + + int error_code; + do { + error_code = avcodec_receive_packet(video_codec_ctx_, pkt); + + if (error_code < 0) { + break; + } + + pkt->stream_index = video_stream_->index; + av_packet_rescale_ts(pkt, video_codec_ctx_->time_base, video_stream_->time_base); + av_interleaved_write_frame(fmt_ctx_, pkt); + av_packet_unref(pkt); + } while (error_code >= 0); + + av_packet_free(&pkt); + } +} + +void FFmpegEncoder::Error(const QString &s) +{ + qWarning() << s; + + Close(); +} diff --git a/app/codec/ffmpeg/ffmpegencoder.h b/app/codec/ffmpeg/ffmpegencoder.h new file mode 100644 index 000000000..40272a620 --- /dev/null +++ b/app/codec/ffmpeg/ffmpegencoder.h @@ -0,0 +1,60 @@ +#ifndef FFMPEGENCODER_H +#define FFMPEGENCODER_H + +extern "C" { +#include +#include +#include +} + +#include "codec/encoder.h" + +class FFmpegEncoder : public Encoder +{ +public: + FFmpegEncoder(const EncodingParams ¶ms); + + virtual bool Open() override; + virtual void Write(FramePtr frame) override; + virtual void Close() override; + +private: + /** + * @brief Handle an error + * + * Immediately closes the Decoder (freeing memory resources) and sends the string provided to the warning stream. + * As this function closes the Decoder, no further Decoder functions should be performed after this is called + * (unless the Decoder is opened again first). + */ + void Error(const QString& s); + + /** + * @brief Handle an FFmpeg error code + * + * Uses the FFmpeg API to retrieve a descriptive string for this error code and sends it to Error(). As such, this + * function also automatically closes the Decoder. + * + * @param error_code + */ + void FFmpegError(int error_code); + + bool InitializeStream(enum AVMediaType type, AVStream** stream, AVCodecContext** codec_ctx, const QString& codec); + bool InitializeCodecContext(AVStream** stream, AVCodecContext** codec_ctx, AVCodec* codec); + bool SetupCodecContext(AVStream *stream, AVCodecContext *codec_ctx, AVCodec *codec); + + void FlushEncoders(); + + AVFormatContext* fmt_ctx_; + + AVStream* video_stream_; + AVCodecContext* video_codec_ctx_; + SwsContext* video_scale_ctx_; + olive::PixelFormat video_conversion_fmt_; + + AVStream* audio_stream_; + AVCodecContext* audio_codec_ctx_; + SwrContext* audio_resample_ctx_; + +}; + +#endif // FFMPEGENCODER_H diff --git a/app/decoder/frame.cpp b/app/codec/frame.cpp similarity index 98% rename from app/decoder/frame.cpp rename to app/codec/frame.cpp index 6b564183d..b4928b04f 100644 --- a/app/decoder/frame.cpp +++ b/app/codec/frame.cpp @@ -30,8 +30,7 @@ Frame::Frame() : height_(0), format_(olive::PIX_FMT_INVALID), sample_count_(0), - timestamp_(0), - native_timestamp_(0) + timestamp_(0) { } diff --git a/app/decoder/frame.h b/app/codec/frame.h similarity index 99% rename from app/decoder/frame.h rename to app/codec/frame.h index 9876d928f..22f465dd2 100644 --- a/app/decoder/frame.h +++ b/app/codec/frame.h @@ -138,8 +138,6 @@ private: rational timestamp_; - int64_t native_timestamp_; - }; #endif // FRAME_H diff --git a/app/decoder/oiio/CMakeLists.txt b/app/codec/oiio/CMakeLists.txt similarity index 92% rename from app/decoder/oiio/CMakeLists.txt rename to app/codec/oiio/CMakeLists.txt index 5a20b7719..19103a192 100644 --- a/app/decoder/oiio/CMakeLists.txt +++ b/app/codec/oiio/CMakeLists.txt @@ -16,7 +16,7 @@ set(OLIVE_SOURCES ${OLIVE_SOURCES} - decoder/oiio/oiiodecoder.h - decoder/oiio/oiiodecoder.cpp + codec/oiio/oiiodecoder.h + codec/oiio/oiiodecoder.cpp PARENT_SCOPE ) diff --git a/app/decoder/oiio/oiiodecoder.cpp b/app/codec/oiio/oiiodecoder.cpp similarity index 100% rename from app/decoder/oiio/oiiodecoder.cpp rename to app/codec/oiio/oiiodecoder.cpp diff --git a/app/decoder/oiio/oiiodecoder.h b/app/codec/oiio/oiiodecoder.h similarity index 98% rename from app/decoder/oiio/oiiodecoder.h rename to app/codec/oiio/oiiodecoder.h index 68dc3cb59..07dd9c2fa 100644 --- a/app/decoder/oiio/oiiodecoder.h +++ b/app/codec/oiio/oiiodecoder.h @@ -23,7 +23,7 @@ #include -#include "decoder/decoder.h" +#include "codec/decoder.h" #include "render/pixelservice.h" class OIIODecoder : public Decoder diff --git a/app/decoder/waveinput.cpp b/app/codec/waveinput.cpp similarity index 100% rename from app/decoder/waveinput.cpp rename to app/codec/waveinput.cpp diff --git a/app/decoder/waveinput.h b/app/codec/waveinput.h similarity index 100% rename from app/decoder/waveinput.h rename to app/codec/waveinput.h diff --git a/app/decoder/waveoutput.cpp b/app/codec/waveoutput.cpp similarity index 100% rename from app/decoder/waveoutput.cpp rename to app/codec/waveoutput.cpp diff --git a/app/decoder/waveoutput.h b/app/codec/waveoutput.h similarity index 100% rename from app/decoder/waveoutput.h rename to app/codec/waveoutput.h diff --git a/app/encoder/CMakeLists.txt b/app/encoder/CMakeLists.txt deleted file mode 100644 index fc7aee057..000000000 --- a/app/encoder/CMakeLists.txt +++ /dev/null @@ -1,24 +0,0 @@ -# Olive - Non-Linear Video Editor -# Copyright (C) 2019 Olive Team -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -#add_subdirectory(oiio) - -set(OLIVE_SOURCES - ${OLIVE_SOURCES} - encoder/encoder.h - encoder/encoder.cpp - PARENT_SCOPE -) diff --git a/app/encoder/encoder.cpp b/app/encoder/encoder.cpp deleted file mode 100644 index 69217ef7a..000000000 --- a/app/encoder/encoder.cpp +++ /dev/null @@ -1,31 +0,0 @@ -#include "encoder.h" - -Encoder::Encoder(const EncodingParams ¶ms) : - params_(params) -{ -} - -EncodingParams::EncodingParams() : - video_enabled_(false), - audio_enabled_(false) -{ -} - -void EncodingParams::SetFilename(const QString &filename) -{ - filename_ = filename; -} - -void EncodingParams::EnableVideo(const VideoParams &video_params, const QString &vcodec) -{ - video_enabled_ = true; - video_params_ = video_params; - video_codec_ = vcodec; -} - -void EncodingParams::EnableAudio(const AudioParams &audio_params, const QString &acodec) -{ - audio_enabled_ = true; - audio_params_ = audio_params; - audio_codec_ = acodec; -} diff --git a/app/encoder/encoder.h b/app/encoder/encoder.h deleted file mode 100644 index 62f9d78eb..000000000 --- a/app/encoder/encoder.h +++ /dev/null @@ -1,49 +0,0 @@ -#ifndef ENCODER_H -#define ENCODER_H - -#include -#include - -#include "common/constructors.h" -#include "render/audioparams.h" -#include "render/videoparams.h" - -class Encoder; -using EncoderPtr = std::shared_ptr; - -class EncodingParams { -public: - EncodingParams(); - - void SetFilename(const QString& filename); - void EnableVideo(const VideoParams& video_params, const QString& vcodec); - void EnableAudio(const AudioParams& audio_params, const QString& acodec); - -private: - QString filename_; - - QString video_codec_; - bool video_enabled_; - VideoParams video_params_; - - QString audio_codec_; - bool audio_enabled_; - AudioParams audio_params_; -}; - -class Encoder -{ -public: - Encoder(const EncodingParams& params); - - DISABLE_COPY_MOVE(Encoder) - - virtual bool Open() = 0; - - virtual void Close() = 0; - -private: - EncodingParams params_; -}; - -#endif // ENCODER_H