整理代码
This commit is contained in:
commit
c7b4e178ec
364 changed files with 59691 additions and 0 deletions
119
video/ffmpeg/AudioDecodeHandler.cpp
Normal file
119
video/ffmpeg/AudioDecodeHandler.cpp
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
44
video/ffmpeg/AudioDecodeHandler.h
Normal file
44
video/ffmpeg/AudioDecodeHandler.h
Normal 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
|
||||
179
video/ffmpeg/AudioEncoder.cpp
Normal file
179
video/ffmpeg/AudioEncoder.cpp
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
45
video/ffmpeg/AudioEncoder.h
Normal file
45
video/ffmpeg/AudioEncoder.h
Normal 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
|
||||
181
video/ffmpeg/AudioResample.cpp
Normal file
181
video/ffmpeg/AudioResample.cpp
Normal 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(); };
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
52
video/ffmpeg/AudioResample.h
Normal file
52
video/ffmpeg/AudioResample.h
Normal 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
|
||||
311
video/ffmpeg/DemuxHandler.cpp
Normal file
311
video/ffmpeg/DemuxHandler.cpp
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
55
video/ffmpeg/DemuxHandler.h
Normal file
55
video/ffmpeg/DemuxHandler.h
Normal 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
295
video/ffmpeg/Muxer.cpp
Normal 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
121
video/ffmpeg/Muxer.h
Normal 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
423
video/ffmpeg/Util.cpp
Normal 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
52
video/ffmpeg/Util.h
Normal 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
|
||||
Loading…
Add table
Add a link
Reference in a new issue