311 lines
11 KiB
C++
311 lines
11 KiB
C++
/*
|
|
*******************************************************************************
|
|
* 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;
|
|
}
|
|
}
|
|
}
|