From 9b6a4e6ef9ba98bbb8cd4bf2c1b35f24ab1fb223 Mon Sep 17 00:00:00 2001 From: Iskandar Safarov Date: Tue, 21 Feb 2017 16:03:10 +0500 Subject: [PATCH] Added proper timebase extraction --- include/transcode/softwareVideoTranscoder.h | 8 ++- include/transcode/videoTranscoder.h | 76 ++++++++++++++++++++- src/main.cpp | 12 ++-- src/transcode/softwareVideoTranscoder.cpp | 50 +++++++++----- src/transcode/videoTranscoder.cpp | 68 +++++++++++++++++- 5 files changed, 185 insertions(+), 29 deletions(-) diff --git a/include/transcode/softwareVideoTranscoder.h b/include/transcode/softwareVideoTranscoder.h index 4203fde..24350a1 100644 --- a/include/transcode/softwareVideoTranscoder.h +++ b/include/transcode/softwareVideoTranscoder.h @@ -20,13 +20,17 @@ namespace fp { SoftwareDecoderContext(StreamType inputType); ~SoftwareDecoderContext() override; size_t decodeFrame(const uint8_t*, size_t, Stream::Metadata*, const DecodedFrameRef&) override; + + void* rawDecoder(); private: + int m_TimeBaseNum = 0; + int m_TimeBaseDen = 1; void* m_Decoder = nullptr; }; class SoftwareEncoderContext : public EncoderContext { public: - SoftwareEncoderContext(const DecodedFrameRef&); + SoftwareEncoderContext(uint32_t width, uint32_t height, uint32_t bitrate, uint32_t timebaseNum, uint32_t timebaseDen); ~SoftwareEncoderContext() override; size_t encodeFrame(const DecodedFrameRef&, std::vector&) override; private: @@ -41,7 +45,7 @@ namespace fp { SoftwareVideoTranscoder(StreamType inputType, const VideoStreamRef& output); protected: DecoderContextRef createDecoder() override; - EncoderContextRef createEncoder(const DecodedFrameRef&) override; + EncoderContextRef createEncoder(uint32_t, uint32_t, uint32_t, uint32_t, uint32_t) override; DecodedFrameRef allocateFrame() override; }; diff --git a/include/transcode/videoTranscoder.h b/include/transcode/videoTranscoder.h index 98e58c4..a5629a5 100644 --- a/include/transcode/videoTranscoder.h +++ b/include/transcode/videoTranscoder.h @@ -16,6 +16,40 @@ namespace fp { * Return true if this frame has earlier presentation time than reference one */ virtual bool operator <(const DecodedFrame& reference) = 0; + + /** + * Frame width + * Set by decoder + */ + uint32_t width(); + void setWidth(uint32_t); + + /** + * Frame height + * Set by decoder + */ + uint32_t height(); + void setHeight(uint32_t); + + /** + * Timebase number + * Set by decoder + */ + uint32_t timebaseNum(); + void setTimebaseNum(uint32_t); + + /** + * Timebase denominator + * Set by decoder + */ + uint32_t timebaseDen(); + void setTimebaseDen(uint32_t); + + private: + uint32_t m_Width = 0; + uint32_t m_Height = 0; + uint32_t m_TimebaseNum = 0; + uint32_t m_TimebaseDen = 1; }; class DecoderContext { @@ -35,8 +69,34 @@ namespace fp { class EncoderContext { public: + EncoderContext(uint32_t width, uint32_t height, uint32_t bitrate, uint32_t timebaseNum, uint32_t timebaseDen); virtual ~EncoderContext() = default; + /** + * Video width + */ + uint32_t width(); + + /** + * Video height + */ + uint32_t height(); + + /** + * Video stream bitrate + */ + uint32_t bitrate(); + + /** + * Timebase number + */ + uint32_t timebaseNum(); + + /** + * Timebase denominator + */ + uint32_t timebaseDen(); + /** * Routine for video frame encoding * @param[in] frame Frame to encode @@ -44,6 +104,13 @@ namespace fp { * @return number of bytes allocated. Return zero if no frame produced (delayed frame) */ virtual size_t encodeFrame(const DecodedFrameRef& frame, std::vector& dest) = 0; + + private: + uint32_t m_Width; + uint32_t m_Height; + uint32_t m_Bitrate; + uint32_t m_TimebaseNum; + uint32_t m_TimebaseDen; }; /** @@ -73,9 +140,14 @@ namespace fp { virtual DecoderContextRef createDecoder() = 0; /** - * Create encoder context based on decoded frame + * Create encoder context + * @param[in] width Output image width + * @param[in] height Output image height + * @param[in] bitrate Output bitstream bitrate, bps + * @param[in] timebaseNum Output bitstream timebase number + * @param[in] timebaseDen Output bitstream timebase denominator */ - virtual EncoderContextRef createEncoder(const DecodedFrameRef&) = 0; + virtual EncoderContextRef createEncoder(uint32_t width, uint32_t height, uint32_t bitrate, uint32_t timebaseNum, uint32_t timebaseDen) = 0; /** * Allocate frame for decoding diff --git a/src/main.cpp b/src/main.cpp index c98f143..ea755af 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -69,9 +69,9 @@ class MyVideoStream : public fp::VideoStream { { // Create transcoder fp::VideoStreamMeta outputMeta; - outputMeta.id = 0; + outputMeta.id = id(); outputMeta.type = fp::StreamType::Video_H264; - outputMeta.sync = true; + outputMeta.sync = sync(); outputMeta.width = width(); outputMeta.height = height(); @@ -156,10 +156,10 @@ class MySource : public fp::cap::FileSource { fp::VideoStreamRef createVideoStream(fp::VideoStreamMeta* meta) override { static int i = 0; - if (i) { + i++; + if (i != 2) { return nullptr; } - i++; return std::make_shared(meta); } @@ -172,8 +172,8 @@ class MySource : public fp::cap::FileSource { int main(int argc, char **argv) { -// fp::cap::FileSource src("mpt-smart-travels-classical-clip.dvb"); - MySource src("football.dvb"); + MySource src("mpt-smart-travels-classical-clip.dvb"); +// MySource src("football.dvb"); // fp::cap::DVBSource src(DVB_ADAPTER, DVB_FRONTEND, DVB_DEMUX); src.start(); diff --git a/src/transcode/softwareVideoTranscoder.cpp b/src/transcode/softwareVideoTranscoder.cpp index 9f8365b..adfbc52 100644 --- a/src/transcode/softwareVideoTranscoder.cpp +++ b/src/transcode/softwareVideoTranscoder.cpp @@ -85,6 +85,10 @@ namespace fp { } } + void* SoftwareDecoderContext::rawDecoder() { + return m_Decoder; + } + size_t SoftwareDecoderContext::decodeFrame(const uint8_t* data, size_t size, Stream::Metadata* metadata, const DecodedFrameRef& outFrame) { if (!outFrame) { throw std::invalid_argument("Provided null output frame"); @@ -111,13 +115,23 @@ namespace fp { int gotOutput = 0; int nres = avcodec_decode_video2((AVCodecContext*)m_Decoder, (AVFrame*)sFrame->rawFrame(), &gotOutput, &decodePacket); if (nres < 0) { - throw std::runtime_error("error decompressing"); + throw std::runtime_error("error decoding frame"); } if (nres) { if (gotOutput) { - ((AVFrame*)sFrame->rawFrame())->pts = ((AVFrame*)sFrame->rawFrame())->pkt_pts; - ((AVFrame*)sFrame->rawFrame())->pict_type = AV_PICTURE_TYPE_S; + // Setup timebase + m_TimeBaseNum = ((AVCodecContext*)m_Decoder)->time_base.num; + m_TimeBaseDen = ((AVCodecContext*)m_Decoder)->time_base.den; + + if (((AVCodecContext*)m_Decoder)->ticks_per_frame) { + m_TimeBaseNum *= ((AVCodecContext*)m_Decoder)->ticks_per_frame; + } + + outFrame->setWidth(((AVFrame*)sFrame->rawFrame())->width); + outFrame->setHeight(((AVFrame*)sFrame->rawFrame())->height); + outFrame->setTimebaseNum(m_TimeBaseNum); + outFrame->setTimebaseDen(m_TimeBaseDen); return nres; } } @@ -127,12 +141,9 @@ namespace fp { // SoftwareEncoderContext /////////////////////////////////////////////////////////////////////////// - SoftwareEncoderContext::SoftwareEncoderContext(const DecodedFrameRef& frame) { - auto sFrame = dynamic_cast(frame.get()); - if (!sFrame) { - throw std::runtime_error("Provided incompatible class implementation"); - } - + SoftwareEncoderContext::SoftwareEncoderContext(uint32_t width, uint32_t height, uint32_t bitrate, uint32_t timebaseNum, uint32_t timebaseDen) + : EncoderContext(width, height, bitrate, timebaseNum, timebaseDen) + { avcodec_register_all(); av_register_all(); @@ -140,15 +151,15 @@ namespace fp { if (h264) { auto encoder = avcodec_alloc_context3(h264); if (encoder) { - encoder->width = ((AVFrame*)sFrame->rawFrame())->width; - encoder->height = ((AVFrame*)sFrame->rawFrame())->height; + encoder->width = width; + encoder->height = height; encoder->sample_aspect_ratio = { 1, 1 }; encoder->pix_fmt = AV_PIX_FMT_YUV420P; - encoder->bit_rate = 1024; + encoder->bit_rate = bitrate / 1024; encoder->codec_id = AV_CODEC_ID_H264; encoder->codec_type = AVMEDIA_TYPE_VIDEO; - encoder->time_base.num = 1001; - encoder->time_base.den = 60000; + encoder->time_base.num = timebaseNum; + encoder->time_base.den = timebaseDen; encoder->flags |= CODEC_FLAG_CLOSED_GOP; auto ret = avcodec_open2(encoder, h264, nullptr); @@ -179,6 +190,9 @@ namespace fp { throw std::invalid_argument("Frame can not be nullptr"); } auto sFrame = dynamic_cast(frame.get()); + if (!sFrame) { + throw std::invalid_argument("Provided incompatible class implementation"); + } AVPacket encodePacket; memset(&encodePacket, 0, sizeof(AVPacket)); @@ -192,6 +206,10 @@ namespace fp { encodePacket.data = data.data(); encodePacket.size = data.size(); + // Copy PTS data for encoder + ((AVFrame*)sFrame->rawFrame())->pts = ((AVFrame*)sFrame->rawFrame())->pkt_pts; + ((AVFrame*)sFrame->rawFrame())->pict_type = AV_PICTURE_TYPE_S; + int gotOutput; auto ret = avcodec_encode_video2((AVCodecContext*)m_Encoder, &encodePacket, (AVFrame*)sFrame->rawFrame(), &gotOutput); if (!ret && gotOutput && ((AVCodecContext*)m_Encoder)->coded_frame) { @@ -225,8 +243,8 @@ namespace fp { return std::make_shared(inputType()); } - EncoderContextRef SoftwareVideoTranscoder::createEncoder(const DecodedFrameRef& frame) { - return std::make_shared(frame); + EncoderContextRef SoftwareVideoTranscoder::createEncoder(uint32_t width, uint32_t height, uint32_t bitrate, uint32_t timebaseNum, uint32_t timebaseDen) { + return std::make_shared(width, height, bitrate, timebaseNum, timebaseDen); } DecodedFrameRef SoftwareVideoTranscoder::allocateFrame() { diff --git a/src/transcode/videoTranscoder.cpp b/src/transcode/videoTranscoder.cpp index 95cd952..b7253c9 100644 --- a/src/transcode/videoTranscoder.cpp +++ b/src/transcode/videoTranscoder.cpp @@ -6,6 +6,66 @@ namespace fp { namespace trans { + uint32_t DecodedFrame::width() { + return m_Width; + } + + void DecodedFrame::setWidth(uint32_t width) { + m_Width = width; + } + + uint32_t DecodedFrame::height() { + return m_Height; + } + + void DecodedFrame::setHeight(uint32_t height) { + m_Height = height; + } + + uint32_t DecodedFrame::timebaseNum() { + return m_TimebaseNum; + } + + void DecodedFrame::setTimebaseNum(uint32_t timebaseNum) { + m_TimebaseNum = timebaseNum; + } + + uint32_t DecodedFrame::timebaseDen() { + return m_TimebaseDen; + } + + void DecodedFrame::setTimebaseDen(uint32_t timebaseDen) { + m_TimebaseDen = timebaseDen; + } + + EncoderContext::EncoderContext(uint32_t width, uint32_t height, uint32_t bitrate, uint32_t timebaseNum, uint32_t timebaseDen) + : m_Width(width) + , m_Height(height) + , m_Bitrate(bitrate) + , m_TimebaseNum(timebaseNum) + , m_TimebaseDen(timebaseDen) + { } + + uint32_t EncoderContext::width() { + return m_Width; + } + + uint32_t EncoderContext::height() { + return m_Height; + } + + uint32_t EncoderContext::bitrate() { + return m_Bitrate; + } + + uint32_t EncoderContext::timebaseNum() { + return m_TimebaseNum; + } + + uint32_t EncoderContext::timebaseDen() { + return m_TimebaseDen; + } + VideoTranscoder::VideoTranscoder(StreamType inputType, const VideoStreamRef& output) : Transcoder(inputType, output) { } @@ -45,7 +105,11 @@ namespace fp { const size_t nBytes = m_DecoderContext->decodeFrame(data, size, metadata, m_DecodedFrame); if (nBytes) { if (!m_EncoderContext) { - auto encoder = createEncoder(m_DecodedFrame); + auto encoder = createEncoder(m_DecodedFrame->width(), + m_DecodedFrame->height(), + 1024*1024, + m_DecodedFrame->timebaseNum(), + m_DecodedFrame->timebaseDen()); if (!encoder) { throw std::runtime_error("Can not initialise encoder"); } @@ -87,8 +151,6 @@ namespace fp { } else { insertFrame(); } - } else { - printf("no frame decompressed\n"); } } catch (std::exception&) { throw;