merged encoder and decoder code into one folder since they can share quite a bit of code
This commit is contained in:
+1
-2
@@ -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)
|
||||
|
||||
@@ -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
|
||||
)
|
||||
@@ -24,8 +24,8 @@
|
||||
#include <QDebug>
|
||||
#include <QFileInfo>
|
||||
|
||||
#include "decoder/ffmpeg/ffmpegdecoder.h"
|
||||
#include "decoder/oiio/oiiodecoder.h"
|
||||
#include "codec/ffmpeg/ffmpegdecoder.h"
|
||||
#include "codec/oiio/oiiodecoder.h"
|
||||
|
||||
Decoder::Decoder() :
|
||||
open_(false),
|
||||
@@ -24,10 +24,10 @@
|
||||
#include <QObject>
|
||||
#include <stdint.h>
|
||||
|
||||
#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<Decoder>;
|
||||
@@ -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<FFmpegEncoder>(params);
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
#ifndef ENCODER_H
|
||||
#define ENCODER_H
|
||||
|
||||
#include <memory>
|
||||
#include <QString>
|
||||
|
||||
#include "codec/frame.h"
|
||||
#include "common/constructors.h"
|
||||
#include "render/audioparams.h"
|
||||
#include "render/videoparams.h"
|
||||
|
||||
class Encoder;
|
||||
using EncoderPtr = std::shared_ptr<Encoder>;
|
||||
|
||||
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
|
||||
@@ -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
|
||||
)
|
||||
@@ -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;
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
#ifndef FFMPEGABSTRACTION_H
|
||||
#define FFMPEGABSTRACTION_H
|
||||
|
||||
extern "C" {
|
||||
#include <libavformat/avformat.h>
|
||||
}
|
||||
|
||||
#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
|
||||
@@ -32,9 +32,10 @@ extern "C" {
|
||||
#include <QString>
|
||||
#include <QtMath>
|
||||
|
||||
#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<AVPixelFormat>(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<olive::PixelFormat>(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<int64_t>(params.channel_layout()),
|
||||
GetFFmpegSampleFormat(params.format()),
|
||||
FFmpegCommon::GetFFmpegSampleFormat(params.format()),
|
||||
params.sample_rate(),
|
||||
static_cast<int64_t>(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
|
||||
@@ -30,8 +30,8 @@ extern "C" {
|
||||
#include <QVector>
|
||||
|
||||
#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_;
|
||||
@@ -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<const uint8_t**>(&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();
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
#ifndef FFMPEGENCODER_H
|
||||
#define FFMPEGENCODER_H
|
||||
|
||||
extern "C" {
|
||||
#include <libavformat/avformat.h>
|
||||
#include <libswscale/swscale.h>
|
||||
#include <libswresample/swresample.h>
|
||||
}
|
||||
|
||||
#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
|
||||
@@ -30,8 +30,7 @@ Frame::Frame() :
|
||||
height_(0),
|
||||
format_(olive::PIX_FMT_INVALID),
|
||||
sample_count_(0),
|
||||
timestamp_(0),
|
||||
native_timestamp_(0)
|
||||
timestamp_(0)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -138,8 +138,6 @@ private:
|
||||
|
||||
rational timestamp_;
|
||||
|
||||
int64_t native_timestamp_;
|
||||
|
||||
};
|
||||
|
||||
#endif // FRAME_H
|
||||
@@ -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
|
||||
)
|
||||
@@ -23,7 +23,7 @@
|
||||
|
||||
#include <OpenImageIO/imageio.h>
|
||||
|
||||
#include "decoder/decoder.h"
|
||||
#include "codec/decoder.h"
|
||||
#include "render/pixelservice.h"
|
||||
|
||||
class OIIODecoder : public Decoder
|
||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
#add_subdirectory(oiio)
|
||||
|
||||
set(OLIVE_SOURCES
|
||||
${OLIVE_SOURCES}
|
||||
encoder/encoder.h
|
||||
encoder/encoder.cpp
|
||||
PARENT_SCOPE
|
||||
)
|
||||
@@ -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;
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
#ifndef ENCODER_H
|
||||
#define ENCODER_H
|
||||
|
||||
#include <memory>
|
||||
#include <QString>
|
||||
|
||||
#include "common/constructors.h"
|
||||
#include "render/audioparams.h"
|
||||
#include "render/videoparams.h"
|
||||
|
||||
class Encoder;
|
||||
using EncoderPtr = std::shared_ptr<Encoder>;
|
||||
|
||||
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
|
||||
Reference in New Issue
Block a user