整理代码

This commit is contained in:
蒲爽 2026-09-28 21:33:04 +08:00
commit c7b4e178ec
364 changed files with 59691 additions and 0 deletions

View file

@ -0,0 +1,119 @@
/*
*******************************************************************************
* INCLUDE
*******************************************************************************
*/
#include "AudioDecodeHandler.h"
#include "Util.h"
namespace Video
{
namespace FFmpeg
{
/*
*******************************************************************************
* CLASS DEFINE
*******************************************************************************
*/
AudioDecodeHandler::AudioDecodeHandler(Core::CodecID codecID, Core::AudioComponent::Ptr pPrevious)
: Core::AudioDecodeHandler(Poco::format("FFmpeg::AudioDecodeHandler(%s)", Core::codecIDToString(codecID)), codecID,
pPrevious)
{
const AVCodec* pCodec = Util::getDecoder(codecID);
if (!pCodec)
throw Poco::NotImplementedException(
Poco::format("%s: Unsupported codec: %s", name(), Core::codecIDToString(codecID)), -EINVAL);
_pCodecCtx = avcodec_alloc_context3(pCodec);
if (!_pCodecCtx)
throw Poco::InvalidAccessException(Poco::format("%s: Could not alloc an encoding context", name()), -EACCES);
_pCodecCtx->ch_layout = Util::getChannelLayout(pPrevious->getChannelLayout());
_pCodecCtx->sample_rate = pPrevious->getSampleRate();
_pCodecCtx->sample_fmt = Util::getSampleFormat(pPrevious->getSampleFormat());
int rc = avcodec_open2(_pCodecCtx, pCodec, nullptr);
if (rc < 0)
throw Poco::InvalidAccessException(
Poco::format("%s: Open %s FAIL: %d(%s)", name(), std::string(pCodec->name), rc, Util::getErrorString(rc)),
rc);
_pFrame = av_frame_alloc();
if (Core::sampleFormatIsPlanar(pPrevious->getSampleFormat()))
{
const int c = Core::channelLayoutChannels(pPrevious->getChannelLayout());
switch (c)
{
case 1:
_frameConvertor = [](AVFrame* from, Core::Frame::Ptr to) -> void
{ memcpy(to->data[0], from->data[0], to->length); };
break;
case 2:
_frameConvertor = [](AVFrame* from, Core::Frame::Ptr to) -> void
{
memcpy(to->data[0], from->data[0], to->length);
memcpy(to->data[1], from->data[1], to->length);
};
break;
default:
throw Poco::BugcheckException(Poco::format("%s: Unsupported channle-count: %d", name(), c), -EINVAL);
}
}
else
{
_frameConvertor = [](AVFrame* from, Core::Frame::Ptr to) -> void
{ memcpy(to->data[0], from->data[0], to->length); };
}
}
AudioDecodeHandler::~AudioDecodeHandler()
{
av_frame_free(&_pFrame);
avcodec_free_context(&_pCodecCtx);
}
void AudioDecodeHandler::write(Core::Packet::Ptr pPacket)
{
AVPacket* pAVPacket;
pPacket->moveTo(&pAVPacket);
int rc = avcodec_send_packet(_pCodecCtx, pAVPacket);
av_packet_free(&pAVPacket);
if (rc < 0)
throw Poco::RuntimeException(Poco::format("%s: Error submitting the packet to the decoder: %d(%s)", name(), rc,
Util::getErrorString(rc)),
rc);
}
Core::Frame::Ptr AudioDecodeHandler::read()
{
int rc = avcodec_receive_frame(_pCodecCtx, _pFrame);
if (rc == AVERROR(EAGAIN) || rc == AVERROR_EOF)
return nullptr;
if (rc < 0)
throw Poco::RuntimeException(
Poco::format("%s: Error during decoding: %d(%s)", name(), rc, Util::getErrorString(rc)), rc);
return convert(_pFrame);
}
Core::Frame::Ptr AudioDecodeHandler::convert(AVFrame* pAVFrame)
{
int length = av_get_bytes_per_sample(_pCodecCtx->sample_fmt) * _pFrame->nb_samples;
Core::Frame::Ptr pFrame = new Core::Frame();
pFrame->length = length;
pFrame->timestamp = pAVFrame->pts;
for (int i = 0; i < pAVFrame->ch_layout.nb_channels; i++)
pFrame->data[i] = new uint8_t[length];
_frameConvertor(pAVFrame, pFrame);
return pFrame;
}
}
}

View file

@ -0,0 +1,44 @@
#ifndef AD6CD053_01C0_4CD4_B6DE_4F9D3940E437
#define AD6CD053_01C0_4CD4_B6DE_4F9D3940E437
/*
*******************************************************************************
* INCLUDE
*******************************************************************************
*/
#include "../core/AudioDecoder.h"
extern "C"
{
#include <libavcodec/avcodec.h>
}
#include <functional>
namespace Video
{
namespace FFmpeg
{
/*
*******************************************************************************
* CLASS DECLARE
*******************************************************************************
*/
class AudioDecodeHandler: public Core::AudioDecodeHandler
{
public:
AudioDecodeHandler(Core::CodecID codecID, Core::AudioComponent::Ptr pPrevious);
~AudioDecodeHandler();
protected:
void write(Core::Packet::Ptr pPacket) override;
Core::Frame::Ptr read() override;
Core::Frame::Ptr convert(AVFrame* pFrame);
private:
AVCodecContext* _pCodecCtx;
AVFrame* _pFrame;
AVPacket* _pPacket;
std::function<void(AVFrame*, Core::Frame::Ptr)> _frameConvertor;
};
}
}
#endif

View file

@ -0,0 +1,179 @@
/*
*******************************************************************************
* INCLUDE
*******************************************************************************
*/
#include "AudioEncoder.h"
#include "Util.h"
#include "Logger.h"
#include "../core/System.h"
namespace Video
{
namespace FFmpeg
{
/*
*******************************************************************************
* CLASS DEFINE
*******************************************************************************
*/
AudioEncoder::AudioEncoder(Core::CodecID codecID)
: Core::AudioEncoder(Poco::format("FFmpeg::AudioEncoder(%s)", Core::codecIDToString(codecID)), codecID),
_pCodecCtx(nullptr), _pFrame(nullptr), _pPacket(nullptr)
{
}
AudioEncoder::~AudioEncoder()
{
}
void AudioEncoder::initialize()
{
auto codecID = getCodecID();
const AVCodec* pCodec = Util::getEncoder(codecID);
if (!pCodec)
throw Poco::NotImplementedException(
Poco::format("%s: Unsupported codec: %s", name(), Core::codecIDToString(codecID)), -EINVAL);
_pCodecCtx = avcodec_alloc_context3(pCodec);
if (!_pCodecCtx)
throw Poco::InvalidAccessException(Poco::format("%s: Could not alloc an encoding context", name()), -EACCES);
setChannelLayout(Core::ChannelLayout::STEREO);
switch (pCodec->id)
{
case AV_CODEC_ID_PCM_ALAW:
case AV_CODEC_ID_PCM_MULAW:
setSampleFormat(Core::SampleFormat::S16P);
setSampleRate(8000);
if (Core::channelLayoutChannels(getChannelLayout()) != 1)
setChannelLayout(Core::ChannelLayout::MONO);
setFrameSize(320);
break;
case AV_CODEC_ID_AAC:
setSampleFormat(Core::SampleFormat::FLTP);
setFrameSize(1024);
break;
case AV_CODEC_ID_MP3:
setSampleFormat(Core::SampleFormat::FLTP);
setFrameSize(1152);
break;
case AV_CODEC_ID_OPUS:
_pCodecCtx->strict_std_compliance = FF_COMPLIANCE_EXPERIMENTAL;
setSampleFormat(Core::SampleFormat::S16);
setFrameSize(960);
break;
default:
throw Poco::BugcheckException(
Poco::format("%s: Unspecified encoder(%s) parameters", name(), Core::codecIDToString(codecID)), -EINVAL);
}
_pCodecCtx->ch_layout = Util::getChannelLayout(getChannelLayout());
_pCodecCtx->sample_rate = getSampleRate();
_pCodecCtx->bit_rate = getBitrate();
_pCodecCtx->sample_fmt = Util::getSampleFormat(getSampleFormat());
_pCodecCtx->time_base = (AVRational){1, getSampleRate()};
int rc = avcodec_open2(_pCodecCtx, pCodec, nullptr);
if (rc < 0)
throw Poco::InvalidAccessException(
Poco::format("%s: Open %s FAIL: %d(%s)", name(), std::string(pCodec->name), rc, Util::getErrorString(rc)),
rc);
if (_pCodecCtx->frame_size == 0)
_pCodecCtx->frame_size = getFrameSize();
_pFrame = av_frame_alloc();
_pFrame->format = _pCodecCtx->sample_fmt;
_pFrame->sample_rate = _pCodecCtx->sample_rate;
_pFrame->nb_samples = _pCodecCtx->frame_size;
av_channel_layout_copy(&_pFrame->ch_layout, &_pCodecCtx->ch_layout);
av_frame_get_buffer(_pFrame, 0);
if (Core::sampleFormatIsPlanar(getSampleFormat()))
{
const int c = Core::channelLayoutChannels(getChannelLayout());
switch (c)
{
case 1:
_frameConvertor = [](Core::Frame::Ptr from, AVFrame* to) -> void
{ memcpy(to->data[0], from->data[0], from->length); };
break;
case 2:
_frameConvertor = [](Core::Frame::Ptr from, AVFrame* to) -> void
{
memcpy(to->data[0], from->data[0], from->length);
memcpy(to->data[1], from->data[1], from->length);
};
break;
default:
throw Poco::BugcheckException(Poco::format("%s: Unsupported channle-count: %d", name(), c), -EINVAL);
}
}
else
{
_frameConvertor = [](Core::Frame::Ptr from, AVFrame* to) -> void
{ memcpy(to->data[0], from->data[0], from->length); };
}
}
void AudioEncoder::deinitialize()
{
av_frame_free(&_pFrame);
avcodec_free_context(&_pCodecCtx);
}
Core::Packet::Ptr AudioEncoder::encode()
{
if (_pPacket == nullptr)
_pPacket = av_packet_alloc();
int rc = avcodec_receive_packet(_pCodecCtx, _pPacket);
if (rc > 0)
{
return new Core::Packet(serialCodeManager().code(), false, &_pPacket);
}
while (true)
{
auto pIn = readFrame();
ts = pIn->timestamp;
rc = av_frame_make_writable(_pFrame);
if (rc < 0)
throw Poco::InvalidAccessException(
Poco::format("%s: Make frame writable failed: %d(%s)", name(), rc, Util::getErrorString(rc)), rc);
_frameConvertor(pIn, _pFrame);
_pFrame->pts = pIn->timestamp;
int rc = avcodec_send_frame(_pCodecCtx, _pFrame);
if (rc < 0)
throw Poco::InvalidAccessException(Poco::format("%s: Sending the frame to the encoder failed: %d(%s)",
name(), rc, Util::getErrorString(rc)),
rc);
rc = avcodec_receive_packet(_pCodecCtx, _pPacket);
if (rc == AVERROR(EAGAIN) || rc == AVERROR_EOF)
continue;
if (rc < 0)
throw Poco::InvalidAccessException(
Poco::format("%s: Error encoding audio frame: %d(%s)", name(), rc, Util::getErrorString(rc)), rc);
return new Core::Packet(serialCodeManager().code(), false, &_pPacket);
}
}
void AudioEncoder::onConfigChanged(ParameterNames& names)
{
Core::AudioEncoder::onConfigChanged(names);
}
}
}

View file

@ -0,0 +1,45 @@
#ifndef D5F58729_9077_452E_ADC5_2D75E17601EA
#define D5F58729_9077_452E_ADC5_2D75E17601EA
/*
*******************************************************************************
* INCLUDE
*******************************************************************************
*/
#include "../core/AudioEncoder.h"
extern "C"
{
#include <libavcodec/avcodec.h>
}
#include <functional>
namespace Video
{
namespace FFmpeg
{
/*
*******************************************************************************
* CLASS DECLARE
*******************************************************************************
*/
class AudioEncoder: public Core::AudioEncoder
{
public:
AudioEncoder(Core::CodecID codecID);
~AudioEncoder();
protected:
void initialize() override;
void deinitialize() override;
Core::Packet::Ptr encode() override;
void onConfigChanged(ParameterNames& names) override;
uint64_t ts = 0;
private:
AVCodecContext* _pCodecCtx;
AVFrame* _pFrame;
AVPacket* _pPacket;
std::function<void(Core::Frame::Ptr, AVFrame*)> _frameConvertor;
};
}
}
#endif

View file

@ -0,0 +1,181 @@
/*
*******************************************************************************
* INCLUDE
*******************************************************************************
*/
#include "AudioResample.h"
#include "Util.h"
#include <Poco/Format.h>
#include <vector>
namespace Video
{
namespace FFmpeg
{
/*
*******************************************************************************
* CLASS DEFINE
*******************************************************************************
*/
AudioResample::AudioResample(Core::ChannelLayout ilayout, Core::SampleFormat iformat, int irate,
Core::ChannelLayout olayout, Core::SampleFormat oformat, int orate)
: Core::AudioResample(Poco::format("FFmpeg::AudioResample(%s:%s:%d->%s:%s:%d)",
Core::channelLayoutToString(ilayout), Core::sampleFormatName(iformat), irate,
Core::channelLayoutToString(olayout), Core::sampleFormatName(oformat), orate),
ilayout, iformat, irate, olayout, oformat, orate),
_pSwrCtx(nullptr)
{
}
AudioResample::~AudioResample()
{
if (_pSwrCtx)
{
swr_close(_pSwrCtx);
swr_free(&_pSwrCtx);
}
}
Core::Frame::Ptr AudioResample::process(Core::Frame::Ptr in)
{
if (!_pSwrCtx)
{
auto iAVLayout = Util::getChannelLayout(inLayout());
auto oAVLayout = Util::getChannelLayout(outLayout());
auto iAVFormat = Util::getSampleFormat(inFormat());
auto oAVFormat = Util::getSampleFormat(outFormat());
int rc = swr_alloc_set_opts2(&_pSwrCtx, &oAVLayout, oAVFormat, outRate(), &iAVLayout, iAVFormat, inRate(), 0,
nullptr);
if (rc < 0)
throw Poco::InvalidArgumentException(
Poco::format("%s: Could not allocate SwrContext: %d(%s)", name(), rc, Util::getErrorString(rc)), rc);
if (outLayout() == Core::ChannelLayout::L || outLayout() == Core::ChannelLayout::R)
{
const int inCh = iAVLayout.nb_channels;
const int outCh = (int)oAVLayout.nb_channels;
const int srcCh = (outLayout() == Core::ChannelLayout::L) ? 0 : inCh - 1;
std::vector<double> matrix(inCh * outCh, 0.0);
matrix[srcCh] = 1.0;
rc = swr_set_matrix(_pSwrCtx, matrix.data(), inCh);
if (rc < 0)
throw Poco::InvalidAccessException(
Poco::format("%s: Could not set SwrContext matrix: %d(%s)", name(), rc, Util::getErrorString(rc)),
rc);
}
rc = swr_init(_pSwrCtx);
if (rc < 0)
throw Poco::InvalidAccessException(
Poco::format("%s: Could not init SwrContext: %d(%s)", name(), rc, Util::getErrorString(rc)), rc);
setAssignPointersMethod(_assignInPointers, inFormat(), inLayout());
setAssignPointersMethod(_assignOutPointers, outFormat(), outLayout());
if (av_sample_fmt_is_planar(iAVFormat))
_iFrameSize = in->length / av_get_bytes_per_sample(iAVFormat);
else
_iFrameSize = in->length / (av_get_bytes_per_sample(iAVFormat) * iAVLayout.nb_channels);
_oSampleSize = av_get_bytes_per_sample(oAVFormat);
if (!av_sample_fmt_is_planar(oAVFormat))
_oSampleSize *= oAVLayout.nb_channels;
_oFrameSize = av_rescale_rnd(swr_get_delay(_pSwrCtx, inRate()) + _iFrameSize, outRate(), inRate(), AV_ROUND_UP);
const int c = Core::channelLayoutChannels(outLayout());
if (Core::sampleFormatIsPlanar(outFormat()))
{
_oBytesPerChannel = av_samples_get_buffer_size(NULL, 1, _oFrameSize, oAVFormat, 1);
switch (c)
{
case 1:
_frameCreator = [this]() -> Core::Frame::Ptr
{
Core::Frame::Ptr pFrame = new Core::Frame;
pFrame->length = _oBytesPerChannel;
pFrame->data[0] = new uint8_t[_oBytesPerChannel << 1];
return pFrame;
};
break;
case 2:
_frameCreator = [this]() -> Core::Frame::Ptr
{
Core::Frame::Ptr pFrame = new Core::Frame;
pFrame->length = _oBytesPerChannel;
pFrame->data[0] = new uint8_t[_oBytesPerChannel << 1];
pFrame->data[1] = new uint8_t[_oBytesPerChannel << 1];
return pFrame;
};
break;
default:
throw Poco::BugcheckException(Poco::format("%s: Unsupported channle-count: %d", name(), c), -EINVAL);
}
}
else
{
_oBytesPerChannel = av_samples_get_buffer_size(NULL, c, _oFrameSize, oAVFormat, 1);
_frameCreator = [this]() -> Core::Frame::Ptr
{
Core::Frame::Ptr pFrame = new Core::Frame;
pFrame->length = _oBytesPerChannel;
pFrame->data[0] = new uint8_t[_oBytesPerChannel << 1];
return pFrame;
};
}
}
Core::Frame::Ptr out = _frameCreator();
out->timestamp = in->timestamp;
uint8_t* iptrs[Core::Frame::DATA_LINES] = {nullptr};
uint8_t* optrs[Core::Frame::DATA_LINES] = {nullptr};
_assignInPointers(iptrs, in);
_assignOutPointers(optrs, out);
int rc = swr_convert(_pSwrCtx, optrs, _oFrameSize, (const uint8_t**)iptrs, _iFrameSize);
if (rc < 0)
throw Poco::InvalidAccessException(
Poco::format("%s: Convert FAIL: %d(%s)", name(), rc, Util::getErrorString(rc)), rc);
out->length = rc * _oSampleSize;
return out;
}
void AudioResample::setAssignPointersMethod(AssignPointersMethod& method, Video::Core::SampleFormat iformat,
Video::Core::ChannelLayout ilayout)
{
if (Core::sampleFormatIsPlanar(iformat))
{
const int c = Core::channelLayoutChannels(ilayout);
switch (c)
{
case 1:
method = [](uint8_t* ptrs[], Core::Frame::Ptr pFrame) -> void { ptrs[0] = pFrame->data[0].get(); };
break;
case 2:
method = [](uint8_t* ptrs[], Core::Frame::Ptr pFrame) -> void
{
ptrs[0] = pFrame->data[0].get();
ptrs[1] = pFrame->data[1].get();
};
break;
default:
throw Poco::BugcheckException(Poco::format("%s: Unsupported channle-count: %d", name(), c), -EINVAL);
}
}
else
{
method = [](uint8_t* ptrs[], Core::Frame::Ptr pFrame) -> void { ptrs[0] = pFrame->data[0].get(); };
}
}
}
}

View file

@ -0,0 +1,52 @@
#ifndef B4406D43_D00D_4DB9_91AB_1428F3FB104C
#define B4406D43_D00D_4DB9_91AB_1428F3FB104C
/*
*******************************************************************************
* INCLUDE
*******************************************************************************
*/
#include "../core/AudioResample.h"
extern "C"
{
#include <libswresample/swresample.h>
}
#include <functional>
namespace Video
{
namespace FFmpeg
{
/*
*******************************************************************************
* CLASS DECLARE
*******************************************************************************
*/
class AudioResample: public Core::AudioResample
{
protected:
using AssignPointersMethod = std::function<void(uint8_t*[], Core::Frame::Ptr)>;
using FrameCreator = std::function<Core::Frame::Ptr(void)>;
public:
AudioResample(Core::ChannelLayout ilayout, Core::SampleFormat iformat, int irate, Core::ChannelLayout olayout,
Core::SampleFormat oformat, int orate);
~AudioResample();
public:
Core::Frame::Ptr process(Core::Frame::Ptr in) override;
void setAssignPointersMethod(AssignPointersMethod& method, Video::Core::SampleFormat iformat,
Video::Core::ChannelLayout ilayout);
private:
SwrContext* _pSwrCtx;
AssignPointersMethod _assignInPointers;
AssignPointersMethod _assignOutPointers;
FrameCreator _frameCreator;
int _oBytesPerChannel;
int _iFrameSize;
int _oFrameSize;
int _oSampleSize;
};
}
}
#endif

View file

@ -0,0 +1,311 @@
/*
*******************************************************************************
* INCLUDE
*******************************************************************************
*/
#include "DemuxHandler.h"
#include "Util.h"
#include "Logger.h"
#include "../core/System.h"
#include "util/Math.h"
#include <list>
namespace Video
{
namespace FFmpeg
{
/*
*******************************************************************************
* CLASS DEFINE
*******************************************************************************
*/
DemuxHandler::DemuxHandler(const std::string& uri)
: Core::DemuxHandler(Poco::format("FFmpeg::DemuxHandler(%s)", uri), uri)
{
}
DemuxHandler::~DemuxHandler()
{
}
void DemuxHandler::run(Core::VideoEncoder::Ptr pVideoEncoder, Core::AudioEncoder::Ptr pAudioEncoder, const Post& post,
std::atomic<bool>& isCancelled)
{
_pFormatCtx = avformat_alloc_context();
_pFormatCtx->interrupt_callback.callback = DemuxHandler::interruptCallback;
_pFormatCtx->interrupt_callback.opaque = this;
std::string url = uri();
bool isFileScheme = (scheme() == "file");
if (isFileScheme)
url = uri().substr(std::string("file:").size());
AVDictionary* pOptions = nullptr;
if (scheme() == "rtsp" && getUsingTCPTransport())
av_dict_set(&pOptions, "rtsp_transport", "tcp", 0);
av_dict_set(&pOptions, "stimeout", "2000000", 0);
setAnchorTime(Core::System::timestamp());
int rc = avformat_open_input(&_pFormatCtx, url.c_str(), nullptr, &pOptions);
if (rc < 0)
throw Poco::InvalidAccessException(
Poco::format("%s: Open input FAIL: %d(%s)", name(), rc, Util::getErrorString(rc)), rc);
if (!isFileScheme)
{
_pFormatCtx->probesize = 256 * 1024;
_pFormatCtx->max_analyze_duration = 300 * 1000;
}
logger().debug("avformat_find_stream_info enter");
rc = avformat_find_stream_info(_pFormatCtx, nullptr);
logger().debug("avformat_find_stream_info exit");
if (rc < 0)
{
avformat_close_input(&_pFormatCtx);
throw Poco::NotFoundException(
Poco::format("%s: Find stream FAIL: %d(%s)", name(), rc, Util::getErrorString(rc)), rc);
}
av_dump_format(_pFormatCtx, 0, url.c_str(), 0);
int vIndex = -1;
AVRational vTimebase;
AVCodecContext* pVidecCodecCtx = nullptr;
if (openCodec(AVMEDIA_TYPE_VIDEO, vIndex, &pVidecCodecCtx) >= 0)
{
auto pStream = _pFormatCtx->streams[vIndex];
int fr = av_q2d(pStream->r_frame_rate);
vTimebase = pStream->time_base;
pVideoEncoder->setCodecID(Util::getCodecID(pVidecCodecCtx->codec_id));
pVideoEncoder->setWidth(pVidecCodecCtx->width);
pVideoEncoder->setHeight(pVidecCodecCtx->height);
pVideoEncoder->setFrameRate(fr);
pVideoEncoder->setGOP(pVidecCodecCtx->gop_size);
pVideoEncoder->setBitrate(pVidecCodecCtx->bit_rate);
}
pVideoEncoder->setSignalLost(vIndex < 0);
int aIndex = -1;
AVRational aTimebase;
AVCodecContext* pAudioCodecCtx = nullptr;
if (openCodec(AVMEDIA_TYPE_AUDIO, aIndex, &pAudioCodecCtx) >= 0)
{
auto pStream = _pFormatCtx->streams[aIndex];
logger().information("%s: Audio found: %s, %dHz, frame-size: %d, %dKbps", name(),
Util::getCodecIDString(pAudioCodecCtx->codec_id), pAudioCodecCtx->sample_rate,
pAudioCodecCtx->frame_size, (int)((float)pAudioCodecCtx->bit_rate / 1000));
aTimebase = pStream->time_base;
pAudioEncoder->setCodecID(Util::getCodecID(pAudioCodecCtx->codec_id));
pAudioEncoder->setSampleFormat(Util::getSampleFormat(pAudioCodecCtx->sample_fmt));
pAudioEncoder->setBitrate(pAudioCodecCtx->bit_rate);
pAudioEncoder->setFrameSize(pAudioCodecCtx->frame_size);
pAudioEncoder->setChannelLayout(Util::getChannelLayout(pAudioCodecCtx->ch_layout));
pAudioEncoder->setSampleRate(pAudioCodecCtx->sample_rate);
}
pAudioEncoder->setSignalLost(aIndex < 0);
pAudioEncoder->commit();
if (vIndex < 0 && aIndex < 0)
{
avformat_close_input(&_pFormatCtx);
throw Poco::NotFoundException(Poco::format("%s: Could NOT find any stream", name()), -ENOSYS);
}
int primaryStream = vIndex >= 0 ? vIndex : aIndex;
AVBSFContext* pAVBSFContext = nullptr;
AVBitStreamFilter* pAVBitStreamFilter = nullptr;
if (vIndex >= 0)
{
if (pVidecCodecCtx->codec_id == AV_CODEC_ID_H264)
pAVBitStreamFilter = (AVBitStreamFilter*)av_bsf_get_by_name("h264_mp4toannexb");
else if (pVidecCodecCtx->codec_id == AV_CODEC_ID_H265)
pAVBitStreamFilter = (AVBitStreamFilter*)av_bsf_get_by_name("hevc_mp4toannexb");
if (pAVBitStreamFilter)
{
av_bsf_alloc(pAVBitStreamFilter, &pAVBSFContext);
avcodec_parameters_copy(pAVBSFContext->par_in, _pFormatCtx->streams[vIndex]->codecpar);
av_bsf_init(pAVBSFContext);
}
if (!pAVBSFContext)
{
avcodec_free_context(&pVidecCodecCtx);
avcodec_free_context(&pAudioCodecCtx);
avformat_close_input(&_pFormatCtx);
throw Poco::NullValueException(Poco::format("%s: Init 'AVBSFContext' FAIL", name()), -ENOSYS);
}
}
if (_seekReqeust)
{
if (isFileScheme && _seekTo < _pFormatCtx->duration)
{
rc = avformat_seek_file(_pFormatCtx, -1, INT64_MIN, _seekTo, INT64_MAX, AVSEEK_FLAG_FRAME);
if (rc < 0)
logger().error("%s: Seek file FAIL: %d(%s)", name(), rc, Util::getErrorString(rc));
else
logger().debug("%s: Seek file SUCCESS", name());
}
_seekReqeust = false;
}
AVPacket* pPacket = nullptr;
pVideoEncoder->commit();
logger().information("%s: Video found: %s, %dx%d@%d, gop: %d, %dKbps", name(),
Util::getCodecIDString(pVidecCodecCtx->codec_id), pVidecCodecCtx->width,
pVidecCodecCtx->height, pVideoEncoder->getFrameRate(), pVidecCodecCtx->gop_size,
(int)((float)pVidecCodecCtx->bit_rate / 1000));
auto postPacket = [&](AVPacket*& pAVPacket) -> void
{
if (pAVPacket->stream_index == primaryStream)
setPosition(
av_rescale_q(pAVPacket->pts, _pFormatCtx->streams[pAVPacket->stream_index]->time_base, {1, 1000000}));
if (pAVPacket->stream_index == vIndex)
{
rc = av_bsf_send_packet(pAVBSFContext, pAVPacket);
av_packet_unref(pAVPacket);
if (rc < 0)
{
logger().error("%s: Send BSF packet FAIL: %d(%s)", name(), rc, Util::getErrorString(rc));
return;
}
while (av_bsf_receive_packet(pAVBSFContext, pAVPacket) >= 0)
{
adjustTimestamp(pAVPacket, vTimebase);
updateTimeline(pAVPacket->pts);
post(new Core::Packet(pVideoEncoder->code(), true, &pAVPacket), Core::MediaType::VIDEO);
}
}
else if (pAVPacket->stream_index == aIndex)
{
adjustTimestamp(pAVPacket, aTimebase);
updateTimeline(pAVPacket->pts);
post(new Core::Packet(pAudioEncoder->code(), false, &pAVPacket), Core::MediaType::AUDIO);
}
else
av_packet_unref(pAVPacket);
};
while (!isCancelled)
{
setAnchorTime(Core::System::timestamp());
if (!pPacket)
pPacket = av_packet_alloc();
if (_seekReqeust)
{
if (isFileScheme && _seekTo < _pFormatCtx->duration)
{
rc = avformat_seek_file(_pFormatCtx, -1, INT64_MIN, _seekTo, INT64_MAX, AVSEEK_FLAG_FRAME);
if (rc < 0)
logger().error("%s: Seek file FAIL: %d(%s)", name(), rc, Util::getErrorString(rc));
else
{
logger().debug("%s: Seek file SUCCESS", name());
setBaseline(getTimeline());
resetTimeDrift();
}
}
_seekReqeust = false;
}
rc = av_read_frame(_pFormatCtx, pPacket);
if (rc < 0)
{
logger().error("%s: Read FAIL: %d(%s)", name(), rc, Util::getErrorString(rc));
break;
}
postPacket(pPacket);
}
av_bsf_free(&pAVBSFContext);
av_packet_free(&pPacket);
avcodec_free_context(&pVidecCodecCtx);
avcodec_free_context(&pAudioCodecCtx);
avformat_close_input(&_pFormatCtx);
}
int DemuxHandler::openCodec(AVMediaType mediaType, int& index, AVCodecContext** pCodecCtx)
{
index = -1;
int rc = av_find_best_stream(_pFormatCtx, mediaType, -1, -1, NULL, 0);
if (rc < 0)
{
logger().warning("%s: Could not find %s stream in input: %d(%s)", name(), Util::getMediaTypeString(mediaType),
rc, Util::getErrorString(rc));
return rc;
}
index = rc;
AVStream* pStream = _pFormatCtx->streams[index];
const AVCodec* pCodec = avcodec_find_decoder(pStream->codecpar->codec_id);
if (!pCodec)
{
logger().error("%s: Failed to find %s codec", name(), Util::getMediaTypeString(mediaType));
return AVERROR(EINVAL);
}
*pCodecCtx = avcodec_alloc_context3(pCodec);
if (!*pCodecCtx)
{
logger().error("%s: Failed to allocate the %s codec context", name(), Util::getMediaTypeString(mediaType));
return AVERROR(ENOMEM);
}
if ((rc = avcodec_parameters_to_context(*pCodecCtx, pStream->codecpar)) < 0)
{
logger().error("%s: Failed to copy %s codec parameters to decoder context: %d(%s)", name(),
Util::getMediaTypeString(mediaType), rc, Util::getErrorString(rc));
return rc;
}
if ((rc = avcodec_open2(*pCodecCtx, pCodec, NULL)) < 0)
{
logger().error("%s: Failed to open %s codec: %d(%s)", name(), Util::getMediaTypeString(mediaType), rc,
Util::getErrorString(rc));
return rc;
}
return 0;
}
void DemuxHandler::adjustTimestamp(AVPacket* pPacket, AVRational& timebase)
{
av_packet_rescale_ts(pPacket, timebase, {1, 1000000});
auto drift = getTimeDrift(pPacket->pts);
pPacket->pts += drift;
pPacket->dts += drift;
}
void DemuxHandler::updateTimeline(uint64_t ts)
{
if (getTimeline() < ts)
setTimeline(ts);
}
void DemuxHandler::seek(uint64_t ts)
{
_seekReqeust = true;
_seekTo = ts;
}
int DemuxHandler::interruptCallback(void* pCtx)
{
return 0;
}
}
}

View file

@ -0,0 +1,55 @@
#ifndef B6199A70_B55C_41FF_ABDD_96D9A657AA29
#define B6199A70_B55C_41FF_ABDD_96D9A657AA29
/*
*******************************************************************************
* INCLUDE
*******************************************************************************
*/
#include "../core/Demuxer.h"
extern "C"
{
#include <libavcodec/avcodec.h>
#include <libavcodec/bsf.h>
#include <libavformat/avformat.h>
#include <libavutil/pixdesc.h>
}
namespace Video
{
namespace FFmpeg
{
/*
*******************************************************************************
* CLASS DECLARE
*******************************************************************************
*/
class DemuxHandler: public Core::DemuxHandler
{
public:
DemuxHandler(const std::string& url);
~DemuxHandler();
public:
void run(Core::VideoEncoder::Ptr pVideoEncoder, Core::AudioEncoder::Ptr pAudioEncoder, const Post& post,
std::atomic<bool>& isCancelled) override;
protected:
PROPERTY_DECLARE(AnchorTime, uint64_t, 0, protected, protected)
protected:
int openCodec(AVMediaType type, int& index, AVCodecContext** pCodecCtx);
void adjustTimestamp(AVPacket* pPacket, AVRational& timebase);
void updateTimeline(uint64_t ts);
void seek(uint64_t) override;
private:
AVFormatContext* _pFormatCtx;
std::atomic<bool> _seekReqeust;
int64_t _seekTo;
protected:
static int interruptCallback(void* pCtx);
};
}
}
#endif

295
video/ffmpeg/Muxer.cpp Normal file
View file

@ -0,0 +1,295 @@
/*
*******************************************************************************
* INCLUDE
*******************************************************************************
*/
#include "Muxer.h"
#include "Util.h"
#include "Logger.h"
#include <Poco/Format.h>
namespace Video
{
namespace FFmpeg
{
/*
*******************************************************************************
* CLASS DEFINE
*******************************************************************************
*/
Muxer::Stream::Stream(AVFormatContext& format, Core::VideoEncoder::Ptr pEncoder, AVRational tb,
Core::PacketQueue::Ptr pQueue)
: _pQueue(pQueue), _timebase(tb), _timeline(0), _pEncoder(nullptr)
{
open(format, pEncoder->getCodecID(),
[&](AVCodecContext* pCodecCtx, AVStream* pStream) -> void
{
int fps = pEncoder->getFrameRate();
pCodecCtx->width = pEncoder->getWidth();
pCodecCtx->height = pEncoder->getHeight();
pCodecCtx->time_base = {1, fps};
pCodecCtx->framerate = {fps, 1};
pCodecCtx->gop_size = pEncoder->getGOP();
pCodecCtx->max_b_frames = 0;
pCodecCtx->pix_fmt = AV_PIX_FMT_YUV420P;
pCodecCtx->bit_rate = pEncoder->getBitrate();
pStream->time_base = pCodecCtx->time_base;
pStream->r_frame_rate = pCodecCtx->framerate;
});
}
Muxer::Stream::Stream(AVFormatContext& format, Core::AudioEncoder::Ptr pEncoder, AVRational tb,
Core::PacketQueue::Ptr pQueue)
: _pQueue(pQueue), _timebase(tb), _timeline(0), _pEncoder(nullptr)
{
const Core::CodecID codecID = pEncoder->getCodecID();
open(format, codecID,
[&](AVCodecContext* pCodecCtx, AVStream* pStream) -> void
{
switch (codecID)
{
case Core::CodecID::G711A:
case Core::CodecID::G711U:
pCodecCtx->sample_fmt = AV_SAMPLE_FMT_S16;
pCodecCtx->ch_layout = AV_CHANNEL_LAYOUT_MONO;
break;
case Core::CodecID::MP3:
case Core::CodecID::AAC:
pCodecCtx->sample_fmt = AV_SAMPLE_FMT_FLTP;
pCodecCtx->ch_layout = AV_CHANNEL_LAYOUT_STEREO;
break;
case Core::CodecID::OPUS:
pCodecCtx->sample_fmt = AV_SAMPLE_FMT_S16;
pCodecCtx->ch_layout = AV_CHANNEL_LAYOUT_STEREO;
break;
default:
throw Poco::NotImplementedException(
Poco::format("Unsupported codec: %s", Core::codecIDToString(codecID)), -EINVAL);
}
pCodecCtx->sample_rate = pEncoder->getSampleRate();
pCodecCtx->bit_rate = pEncoder->getBitrate();
pStream->time_base = (AVRational){1, pCodecCtx->sample_rate};
});
}
Muxer::Stream::~Stream()
{
close();
}
void Muxer::Stream::open(AVFormatContext& format, const Core::CodecID codec,
std::function<void(AVCodecContext*, AVStream*)> init)
{
_pEncoder = nullptr;
const AVCodec* pCodec = Util::getEncoder(codec);
if (!pCodec)
throw Poco::NotImplementedException(Poco::format("Unsupported codec: %s", Core::codecIDToString(codec)),
-EINVAL);
_pStream = avformat_new_stream(&format, pCodec);
if (!_pStream)
throw Poco::OutOfMemoryException("Alloc stream object FAIL", -ENOMEM);
_pStream->id = format.nb_streams - 1;
_pEncoder = avcodec_alloc_context3(pCodec);
if (!_pEncoder)
throw Poco::NotImplementedException(Poco::format("Unsupported codec: %s", Core::codecIDToString(codec)),
-EINVAL);
_pEncoder->codec_id = pCodec->id;
init(_pEncoder, _pStream);
if (format.oformat->flags & AVFMT_GLOBALHEADER)
_pEncoder->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
int rc = avcodec_open2(_pEncoder, pCodec, NULL);
if (rc < 0)
throw Poco::RuntimeException(
Poco::format("Open codec(%s) FAIL: %d(%s)", Core::codecIDToString(codec), rc, Util::getErrorString(rc)),
rc);
avcodec_parameters_from_context(_pStream->codecpar, _pEncoder);
}
void Muxer::Stream::close()
{
avcodec_free_context(&_pEncoder);
}
/*
*******************************************************************************
* CLASS DEFINE
*******************************************************************************
*/
Muxer::Muxer(const std::string& url): Core::Muxer(Poco::format("FFmpeg::Muxer(%s)", url), url)
{
}
Muxer::~Muxer()
{
}
void Muxer::connect()
{
const std::string format = getFormatName();
int rc =
avformat_alloc_output_context2(&_pFormatCtx, nullptr, format.empty() ? nullptr : format.c_str(), url().c_str());
if (rc < 0)
throw Poco::NotFoundException(Poco::format("%s: Create output format FAIL: %s: %s", name(), scheme(), format),
-EINVAL);
AVRational vtb, atb;
getSuitableTimebase(vtb, atb);
previousComponents().foreach (
[&](Core::Component::Ptr pPrevious) -> void
{
auto pVideoEncoder = pPrevious.cast<Core::VideoEncoder>();
if (!pVideoEncoder.isNull())
{
_streams.push_back(new Stream(*_pFormatCtx, pVideoEncoder, vtb, packetQueues().find(pPrevious)));
return;
}
auto pAudioEncoder = pPrevious.cast<Core::AudioEncoder>();
if (!pAudioEncoder.isNull())
{
_streams.push_back(new Stream(*_pFormatCtx, pAudioEncoder, atb, packetQueues().find(pPrevious)));
}
});
if (!(_pFormatCtx->oformat->flags & AVFMT_NOFILE))
{
AVDictionary* pOptions = nullptr;
av_dict_set(&pOptions, "rw_timeout", "10000", 0);
if ("udp" == scheme())
{
av_dict_set(&pOptions, "pkt_size", "1316", 0);
}
rc = avio_open2(&_pFormatCtx->pb, url().c_str(), AVIO_FLAG_WRITE, nullptr, &pOptions);
if (rc < 0)
throw Poco::RuntimeException(
Poco::format("%s: Open: '%s' FAIL: %s", name(), url(), Util::getErrorString(rc)), rc);
}
AVDictionary* pOptions = NULL;
if ("rtsp" == scheme())
{
av_dict_set(&pOptions, "rtsp_transport", "tcp", 0);
av_dict_set(&pOptions, "buffer_size", "1024000", 0);
}
rc = avformat_write_header(_pFormatCtx, &pOptions);
if (rc < 0)
throw Poco::RuntimeException(
Poco::format("%s: Open output: '%s' FAIL: %s", name(), url(), Util::getErrorString(rc)), rc);
}
void Muxer::mux()
{
static const AVRational timebase = {1, 1000000};
std::sort(_streams.begin(), _streams.end(), [](auto a, auto b) { return *a < *b; });
auto& pStream = *_streams.begin();
Core::Packet::Ptr pPacket = pStream->queue()->dequeue(4000);
if (pStream->serialCodeWatcher().isChanged(pPacket->serialCode()))
throw Poco::ApplicationException(Poco::format("%s: Stream changed, restart...", name()));
AVPacket* pAVPacket;
pPacket->moveTo(&pAVPacket);
pAVPacket->stream_index = pStream->id();
pStream->setTimeline(pAVPacket->pts);
av_packet_rescale_ts(pAVPacket, timebase, pStream->timebase());
int rc = av_interleaved_write_frame(_pFormatCtx, pAVPacket);
av_packet_free(&pAVPacket);
if (rc < 0)
{
logger().warning("%s: mux FAIL: %d (%s)", name(), rc, Util::getErrorString(rc));
if (rc != -22)
throw Poco::RuntimeException(Poco::format("%s: mux FAIL: %d (%s)", name(), rc, Util::getErrorString(rc)),
rc);
}
}
void Muxer::disconnect()
{
if (_pFormatCtx && _pFormatCtx->pb)
{
av_write_trailer(_pFormatCtx);
if (!(_pFormatCtx->oformat->flags & AVFMT_NOFILE))
avio_close(_pFormatCtx->pb);
}
_streams.clear();
avformat_free_context(_pFormatCtx);
}
const std::string Muxer::getFormatName()
{
const std::string s = scheme();
if (s == "rtmp")
return "flv";
if (s == "rtsp")
return "rtsp";
if (s == "udp")
return "mpegts";
return "";
}
void Muxer::getSuitableTimebase(AVRational& vtb, AVRational& atb)
{
const std::string s = scheme();
if ("rtmp" == s)
{
atb = {1, 1000};
vtb = {1, 1000};
}
else if ("rtsp" == s)
{
previousComponents().foreach (
[&](Core::Component::Ptr pPrevious) -> void
{
auto pEncoder = pPrevious.cast<Core::AudioEncoder>();
if (!pEncoder.isNull())
{
switch (pEncoder->getCodecID())
{
case Core::CodecID::AAC:
case Core::CodecID::OPUS:
atb = {1, pEncoder->getSampleRate()};
break;
case Core::CodecID::G711A:
case Core::CodecID::G711U:
atb = {1, 8000};
break;
default:
atb = {1, 90000};
break;
}
}
});
vtb = {1, 90000};
}
else if ("udp" == s)
{
atb = {1, 90000};
vtb = {1, 90000};
}
}
}
}

121
video/ffmpeg/Muxer.h Normal file
View file

@ -0,0 +1,121 @@
#ifndef DE90A7C0_21FB_4520_99C8_4A6D864A7F12
#define DE90A7C0_21FB_4520_99C8_4A6D864A7F12
/*
*******************************************************************************
* INCLUDE
*******************************************************************************
*/
#include "../core/Muxer.h"
#include "../core/VideoEncoder.h"
#include "../core/AudioEncoder.h"
extern "C"
{
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
}
#include <functional>
#include <vector>
namespace Video
{
namespace FFmpeg
{
/*
*******************************************************************************
* CLASS DECLARE
*******************************************************************************
*/
class Muxer: public Core::Muxer
{
protected:
class Stream
{
public:
using Ptr = Poco::SharedPtr<Stream>;
public:
Stream(AVFormatContext& format, Core::VideoEncoder::Ptr pEncoder, AVRational tb, Core::PacketQueue::Ptr pQueue);
Stream(AVFormatContext& format, Core::AudioEncoder::Ptr pEncoder, AVRational tb, Core::PacketQueue::Ptr pQueue);
~Stream();
public:
int id() const;
bool operator<(const Stream& other);
Core::PacketQueue::Ptr queue();
AVRational timebase();
uint64_t getTimeline();
void setTimeline(uint64_t t);
Core::SerialCodeWatcher& serialCodeWatcher();
protected:
void open(AVFormatContext& format, const Core::CodecID codec,
std::function<void(AVCodecContext*, AVStream*)> init);
void close();
private:
AVStream* _pStream;
Core::PacketQueue::Ptr _pQueue;
AVRational _timebase;
uint64_t _timeline;
AVCodecContext* _pEncoder;
Core::SerialCodeWatcher _serialCodeWatcher;
};
public:
Muxer(const std::string& url);
~Muxer();
protected:
void connect() override;
void mux() override;
void disconnect() override;
const std::string getFormatName();
void getSuitableTimebase(AVRational& vtb, AVRational& atb);
private:
AVFormatContext* _pFormatCtx;
std::vector<Stream::Ptr> _streams;
};
inline bool Muxer::Stream::operator<(const Stream& other)
{
return _timeline < other._timeline;
}
inline int Muxer::Stream::id() const
{
return _pStream->id;
}
inline Core::PacketQueue::Ptr Muxer::Stream::queue()
{
return _pQueue;
}
inline AVRational Muxer::Stream::timebase()
{
return _timebase;
}
inline uint64_t Muxer::Stream::getTimeline()
{
return _timeline;
}
inline void Muxer::Stream::setTimeline(uint64_t t)
{
_timeline = t;
}
inline Core::SerialCodeWatcher& Muxer::Stream::serialCodeWatcher()
{
return _serialCodeWatcher;
}
}
}
#endif

423
video/ffmpeg/Util.cpp Normal file
View file

@ -0,0 +1,423 @@
/*
*******************************************************************************
* INCLUDE
*******************************************************************************
*/
#include "Util.h"
#include <Poco/Exception.h>
#include <Poco/Format.h>
#include <array>
namespace Video
{
namespace FFmpeg
{
/*
*******************************************************************************
* TYPEDEFS
*******************************************************************************
*/
struct CodecIDItem
{
Core::CodecID cid;
AVCodecID avid;
};
struct SampleFormatItem
{
Core::SampleFormat cfmt;
AVSampleFormat avfmt;
};
/*
*******************************************************************************
* LOCAL VARIABLES
*******************************************************************************
*/
static const std::array<CodecIDItem, 8> codecMap = {{
{Core::CodecID::H264, AV_CODEC_ID_H264},
{Core::CodecID::H265, AV_CODEC_ID_H265},
{Core::CodecID::MJPEG, AV_CODEC_ID_MJPEG},
{Core::CodecID::MP3, AV_CODEC_ID_MP3},
{Core::CodecID::AAC, AV_CODEC_ID_AAC},
{Core::CodecID::OPUS, AV_CODEC_ID_OPUS},
{Core::CodecID::G711A, AV_CODEC_ID_PCM_ALAW},
{Core::CodecID::G711U, AV_CODEC_ID_PCM_MULAW},
}};
static const std::array<SampleFormatItem, 12> formatMap = {{
{
Core::SampleFormat::U8,
AV_SAMPLE_FMT_U8,
},
{
Core::SampleFormat::S16,
AV_SAMPLE_FMT_S16,
},
{
Core::SampleFormat::S32,
AV_SAMPLE_FMT_S32,
},
{
Core::SampleFormat::S64,
AV_SAMPLE_FMT_S64,
},
{
Core::SampleFormat::FLT,
AV_SAMPLE_FMT_FLT,
},
{
Core::SampleFormat::DBL,
AV_SAMPLE_FMT_DBL,
},
{
Core::SampleFormat::U8P,
AV_SAMPLE_FMT_U8P,
},
{
Core::SampleFormat::S16P,
AV_SAMPLE_FMT_S16P,
},
{
Core::SampleFormat::S32P,
AV_SAMPLE_FMT_S32P,
},
{
Core::SampleFormat::S64P,
AV_SAMPLE_FMT_S64P,
},
{
Core::SampleFormat::FLTP,
AV_SAMPLE_FMT_FLTP,
},
{
Core::SampleFormat::DBLP,
AV_SAMPLE_FMT_DBLP,
},
}};
/*
*******************************************************************************
* FUNCTIONS
*******************************************************************************
*/
static Core::CodecID findCodecID(AVCodecID id)
{
for (const auto& m : codecMap)
{
if (id == m.avid)
return m.cid;
}
throw Poco::NotFoundException(
Poco::format("Video::FFmpeg: Can't find %s in codec-map", Util::getCodecIDString(id)));
}
static AVCodecID findCodecID(Core::CodecID id)
{
for (const auto& m : codecMap)
{
if (id == m.cid)
return m.avid;
}
throw Poco::NotFoundException(Poco::format("Video::FFmpeg: Can't find %s in codec-map", Core::codecIDToString(id)));
}
/*
*******************************************************************************
* CLASS DEFINE
*******************************************************************************
*/
std::string Util::getMediaTypeString(AVMediaType mediaType)
{
return av_get_media_type_string(mediaType);
}
std::string Util::getErrorString(int err)
{
char buf[AV_ERROR_MAX_STRING_SIZE];
return av_make_error_string(buf, AV_ERROR_MAX_STRING_SIZE, err);
}
std::string Util::getCodecIDString(AVCodecID id)
{
const char* name = avcodec_get_name(id);
if (name != nullptr)
return name;
return Poco::format("UNKNOWN(%u)", (uint32_t)id);
}
std::string Util::getSampleFormatString(AVSampleFormat fmt)
{
const char* name = av_get_sample_fmt_name(fmt);
if (name != nullptr)
return name;
return Poco::format("UNKNOWN(%u)", (uint32_t)fmt);
}
const AVCodec* Util::getEncoder(Core::CodecID codec)
{
return avcodec_find_encoder(findCodecID(codec));
}
const AVCodec* Util::getDecoder(Core::CodecID codec)
{
return avcodec_find_decoder(findCodecID(codec));
}
const Core::CodecID Util::getCodecID(AVCodecID id)
{
return findCodecID(id);
}
AVChannelLayout Util::getChannelLayout(Core::ChannelLayout layout)
{
switch (layout)
{
case Core::ChannelLayout::MONO:
case Core::ChannelLayout::L:
case Core::ChannelLayout::R:
return AV_CHANNEL_LAYOUT_MONO;
default:
case Core::ChannelLayout::STEREO:
return AV_CHANNEL_LAYOUT_STEREO;
}
}
Core::ChannelLayout Util::getChannelLayout(AVChannelLayout layout)
{
AVChannelLayout other = AV_CHANNEL_LAYOUT_STEREO;
if (av_channel_layout_compare(&layout, &other) == 0)
return Core::ChannelLayout::STEREO;
return Core::ChannelLayout::MONO;
}
AVSampleFormat Util::getSampleFormat(Core::SampleFormat format)
{
for (const auto& m : formatMap)
{
if (format == m.cfmt)
return m.avfmt;
}
throw Poco::NotFoundException(
Poco::format("Video::FFmpeg: Can't find %s in format-map", Core::sampleFormatName(format)));
}
Core::SampleFormat Util::getSampleFormat(AVSampleFormat format)
{
for (const auto& m : formatMap)
{
if (format == m.avfmt)
return m.cfmt;
}
throw Poco::NotFoundException(
Poco::format("Video::FFmpeg: Can't find %s in format-map", getSampleFormatString(format)));
}
int Util::convertJPG2NV16(std::string srcPath, std::string dstPath)
{
#define TARGET_WIDTH 1920
#define TARGET_HEIGHT 1080
const char* input_filename = srcPath.c_str();
const char* output_filename = dstPath.c_str();
AVFormatContext* fmt_ctx = NULL;
AVCodecContext* codec_ctx = NULL;
const AVCodec* codec = NULL;
AVFrame * frame = NULL, *scaled_frame = NULL;
AVPacket* pkt = NULL;
SwsContext* sws_ctx = NULL;
FILE* outfile = NULL;
int ret = 0;
int video_stream_idx = -1;
if ((ret = avformat_open_input(&fmt_ctx, input_filename, NULL, NULL)) < 0)
{
fprintf(stderr, "Cannot open input file\n");
return -1;
}
if ((ret = avformat_find_stream_info(fmt_ctx, NULL)) < 0)
{
fprintf(stderr, "Cannot find stream information\n");
return -1;
}
for (int i = 0; i < fmt_ctx->nb_streams; i++)
{
if (fmt_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
{
video_stream_idx = i;
break;
}
}
if (video_stream_idx == -1)
{
fprintf(stderr, "No video stream found\n");
ret = -1;
return -1;
}
AVCodecParameters* codecpar = fmt_ctx->streams[video_stream_idx]->codecpar;
codec = avcodec_find_decoder(codecpar->codec_id);
if (!codec)
{
fprintf(stderr, "Unsupported codec\n");
ret = -1;
return -1;
}
codec_ctx = avcodec_alloc_context3(codec);
if (!codec_ctx)
{
fprintf(stderr, "Cannot allocate codec context\n");
ret = -1;
return -1;
}
if ((ret = avcodec_parameters_to_context(codec_ctx, codecpar)) < 0)
{
fprintf(stderr, "Cannot copy codec parameters\n");
return -1;
}
if ((ret = avcodec_open2(codec_ctx, codec, NULL)) < 0)
{
fprintf(stderr, "Cannot open codec\n");
return -1;
}
frame = av_frame_alloc();
if (!frame)
{
fprintf(stderr, "Cannot allocate frame\n");
ret = -1;
return -1;
}
scaled_frame = av_frame_alloc();
if (!scaled_frame)
{
fprintf(stderr, "Cannot allocate scaled frame\n");
ret = -1;
return -1;
}
scaled_frame->width = TARGET_WIDTH;
scaled_frame->height = TARGET_HEIGHT;
scaled_frame->format = AV_PIX_FMT_YUV422P;
if ((ret = av_frame_get_buffer(scaled_frame, 32)) < 0)
{
fprintf(stderr, "Cannot allocate scaled frame buffer\n");
return -1;
}
sws_ctx = sws_getContext(codec_ctx->width, codec_ctx->height, codec_ctx->pix_fmt, TARGET_WIDTH, TARGET_HEIGHT,
AV_PIX_FMT_YUV422P, SWS_BILINEAR, NULL, NULL, NULL);
if (!sws_ctx)
{
fprintf(stderr, "Cannot create sws context\n");
ret = -1;
return -1;
}
outfile = fopen(output_filename, "wb");
if (!outfile)
{
fprintf(stderr, "Cannot open output file\n");
ret = -1;
return -1;
}
pkt = av_packet_alloc();
if (!pkt)
{
fprintf(stderr, "Cannot allocate packet\n");
ret = -1;
return -1;
}
while (av_read_frame(fmt_ctx, pkt) >= 0)
{
if (pkt->stream_index == video_stream_idx)
{
ret = avcodec_send_packet(codec_ctx, pkt);
if (ret < 0)
{
fprintf(stderr, "Error sending packet\n");
break;
}
while (ret >= 0)
{
ret = avcodec_receive_frame(codec_ctx, frame);
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
break;
else if (ret < 0)
{
fprintf(stderr, "Error receiving frame\n");
break;
}
sws_scale(sws_ctx, (const uint8_t* const*)frame->data, frame->linesize, 0, frame->height,
scaled_frame->data, scaled_frame->linesize);
int width = scaled_frame->width;
int height = scaled_frame->height;
for (int i = 0; i < height; i++)
{
fwrite(scaled_frame->data[0] + i * scaled_frame->linesize[0], 1, width, outfile);
}
for (int i = 0; i < height; i++)
{
uint8_t* u_line = scaled_frame->data[1] + i * scaled_frame->linesize[1];
uint8_t* v_line = scaled_frame->data[2] + i * scaled_frame->linesize[2];
for (int j = 0; j < width / 2; j++)
{
fwrite(&v_line[j], 1, 1, outfile);
fwrite(&u_line[j], 1, 1, outfile);
}
}
printf("Image decoded and converted: %dx%d -> %dx%d\n", frame->width, frame->height, TARGET_WIDTH,
TARGET_HEIGHT);
}
}
av_packet_unref(pkt);
break;
}
printf("Conversion completed. Output saved to %s\n", output_filename);
if (outfile)
fclose(outfile);
if (sws_ctx)
sws_freeContext(sws_ctx);
if (pkt)
av_packet_free(&pkt);
if (frame)
av_frame_free(&frame);
if (scaled_frame)
av_frame_free(&scaled_frame);
if (codec_ctx)
avcodec_free_context(&codec_ctx);
if (fmt_ctx)
avformat_close_input(&fmt_ctx);
return ret < 0 ? -1 : 0;
}
}
}

52
video/ffmpeg/Util.h Normal file
View file

@ -0,0 +1,52 @@
#ifndef CAEDF84E_DEDF_40BF_A293_86ED2D0A044A
#define CAEDF84E_DEDF_40BF_A293_86ED2D0A044A
/*
*******************************************************************************
* INCLUDE
*******************************************************************************
*/
#include "../core/CodecID.h"
#include "../core/ChannelLayout.h"
#include "../core/SampleFormat.h"
extern "C"
{
#include <libavutil/avutil.h>
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavutil/imgutils.h>
#include <libswscale/swscale.h>
}
#include <string>
namespace Video
{
namespace FFmpeg
{
/*
*******************************************************************************
* CLASS DECLARE
*******************************************************************************
*/
class Util
{
public:
static std::string getMediaTypeString(enum AVMediaType mediaType);
static std::string getErrorString(int err);
static std::string getCodecIDString(AVCodecID id);
static std::string getSampleFormatString(AVSampleFormat fmt);
static const AVCodec* getEncoder(Core::CodecID codec);
static const AVCodec* getDecoder(Core::CodecID codec);
static const Core::CodecID getCodecID(AVCodecID id);
static AVChannelLayout getChannelLayout(Core::ChannelLayout layout);
static Core::ChannelLayout getChannelLayout(AVChannelLayout layout);
static AVSampleFormat getSampleFormat(Core::SampleFormat format);
static Core::SampleFormat getSampleFormat(AVSampleFormat format);
static int convertJPG2NV16(std::string srcPath, std::string dstPath);
};
}
}
#endif