From 2a2ab38bf5c4fcfca7097a33ae83cfc8e6599f3a Mon Sep 17 00:00:00 2001 From: Selim Mustafaev Date: Tue, 18 Oct 2016 23:58:06 +0300 Subject: [PATCH] more C++ wrappers --- CMakeLists.txt | 6 +-- ffcpp/Codec.cpp | 25 +++++++++ ffcpp/Codec.h | 8 ++- ffcpp/Frame.cpp | 42 +++++++++++++++ ffcpp/Frame.h | 29 ++++++++++ ffcpp/MediaFile.cpp | 24 ++++++++- ffcpp/MediaFile.h | 4 +- ffcpp/Packet.cpp | 43 +++++++++++++++ ffcpp/Packet.h | 12 +++++ ffcpp/Stream.cpp | 2 +- main.cpp | 129 +++++++++++++------------------------------- 11 files changed, 226 insertions(+), 98 deletions(-) create mode 100644 ffcpp/Frame.cpp create mode 100644 ffcpp/Frame.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 67eadb1..65b70ae 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,8 +2,8 @@ cmake_minimum_required(VERSION 3.5) project(ffConv) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/modules/") -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14 -ggdb -Og") -set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS} -std=c++14 -ggdb -Og") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14 -ggdb -O2") +set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS} -std=c++14 -ggdb -O0") find_package(FFMPEG REQUIRED) include_directories(${FFMPEG_INCLUDE_DIRS}) @@ -11,6 +11,6 @@ link_directories(${FFMPEG_LIBRARY_DIRS}) #message(FATAL_ERROR ${FFMPEG_LIBRARIES}) -set(SOURCE_FILES main.cpp ffcpp/MediaFile.cpp ffcpp/MediaFile.h ffcpp/ffcpp.cpp ffcpp/ffcpp.h ffcpp/Stream.cpp ffcpp/Stream.h ffcpp/Codec.cpp ffcpp/Codec.h ffcpp/Packet.cpp ffcpp/Packet.h) +set(SOURCE_FILES main.cpp ffcpp/MediaFile.cpp ffcpp/MediaFile.h ffcpp/ffcpp.cpp ffcpp/ffcpp.h ffcpp/Stream.cpp ffcpp/Stream.h ffcpp/Codec.cpp ffcpp/Codec.h ffcpp/Packet.cpp ffcpp/Packet.h ffcpp/Frame.cpp ffcpp/Frame.h) add_executable(ffConv ${SOURCE_FILES}) target_link_libraries(ffConv ${FFMPEG_LIBRARIES}) \ No newline at end of file diff --git a/ffcpp/Codec.cpp b/ffcpp/Codec.cpp index 2a3e6a1..a6c6ea3 100644 --- a/ffcpp/Codec.cpp +++ b/ffcpp/Codec.cpp @@ -83,4 +83,29 @@ namespace ffcpp { return *this; } + Frame Codec::decode(Packet &packet) { + Frame frame; + int gotPicture = 0; + auto decFunc = (_codecCtx->codec_type == AVMEDIA_TYPE_VIDEO ? avcodec_decode_video2 : avcodec_decode_audio4); + + while(!gotPicture) { + int res = decFunc(_codecCtx, frame, &gotPicture, packet); + if(res < 0) throw std::runtime_error("cannot decode packet"); + } + + frame.guessPts(); + return frame; + } + + Packet Codec::encode(AVFrame* frame) { + Packet packet; + int gotPacket = 0; + auto encFunc = (_codecCtx->codec_type == AVMEDIA_TYPE_VIDEO ? avcodec_encode_video2 : avcodec_encode_audio2); + + int res = encFunc(_codecCtx, packet, frame, &gotPacket); + if(res < 0) throw std::runtime_error("cannot encode frame"); + + return packet; + } + } diff --git a/ffcpp/Codec.h b/ffcpp/Codec.h index e1728be..46bc2af 100644 --- a/ffcpp/Codec.h +++ b/ffcpp/Codec.h @@ -1,6 +1,9 @@ #ifndef FFCONV_CODEC_H #define FFCONV_CODEC_H +#include "Packet.h" +#include "Frame.h" + extern "C" { #include } @@ -14,8 +17,8 @@ namespace ffcpp { class Codec { private: - AVCodecContext* _codecCtx; AVCodec* _codec; + AVCodecContext* _codecCtx; public: Codec(); @@ -35,6 +38,9 @@ namespace ffcpp { void setHeight(int height); void setPixelFormat(AVPixelFormat pixelFormat); + Frame decode(Packet& packet); + Packet encode(AVFrame* frame); + public: Codec(Codec&& c); Codec& operator=(Codec&& c); diff --git a/ffcpp/Frame.cpp b/ffcpp/Frame.cpp new file mode 100644 index 0000000..97c610d --- /dev/null +++ b/ffcpp/Frame.cpp @@ -0,0 +1,42 @@ +#include "Frame.h" +#include + +namespace ffcpp { + + Frame::Frame() { + _frame = av_frame_alloc(); + } + + Frame::Frame(Frame &&frame) { + *this = std::move(frame); + } + + Frame::~Frame() { + if(_frame) { + av_frame_free(&_frame); + } + } + + Frame& Frame::operator=(Frame&& frame) { + _frame = frame._frame; + frame._frame = nullptr; + return *this; + } + + Frame::operator AVFrame*() { + return _frame; + } + + Frame::operator const AVFrame*() const { + return _frame; + } + + void Frame::guessPts() { + _frame->pts = av_frame_get_best_effort_timestamp(_frame); + } + + void Frame::setPictureType(AVPictureType type) { + _frame->pict_type = type; + } + +} diff --git a/ffcpp/Frame.h b/ffcpp/Frame.h new file mode 100644 index 0000000..a9f0f05 --- /dev/null +++ b/ffcpp/Frame.h @@ -0,0 +1,29 @@ +#ifndef FFCONV_FRAME_H +#define FFCONV_FRAME_H + +extern "C" { + #include +} + +namespace ffcpp { + + class Frame { + private: + AVFrame* _frame; + + public: + Frame(); + Frame(Frame&& frame); + ~Frame(); + + Frame& operator=(Frame&& frame); + operator AVFrame*(); + operator const AVFrame*() const; + + void guessPts(); + void setPictureType(AVPictureType type); + }; + +} + +#endif //FFCONV_FRAME_H diff --git a/ffcpp/MediaFile.cpp b/ffcpp/MediaFile.cpp index 2011a25..f3783df 100644 --- a/ffcpp/MediaFile.cpp +++ b/ffcpp/MediaFile.cpp @@ -5,6 +5,7 @@ namespace ffcpp { MediaFile::MediaFile(const std::string& src, Mode mode) { + _formatCtx = nullptr; _mode = mode; if(mode == Mode::Read) { int res = avformat_open_input(&_formatCtx, src.c_str(), nullptr, nullptr); @@ -13,8 +14,9 @@ namespace ffcpp { res = avformat_find_stream_info(_formatCtx, nullptr); throwIfError(res, "cannot find stream info"); + _streams.reserve(_formatCtx->nb_streams); for(size_t i = 0; i < _formatCtx->nb_streams; ++i) { - _streams.push_back(Stream(_formatCtx->streams[i])); + _streams.emplace_back(_formatCtx->streams[i]); } } else if(mode == Mode::Write) { int res = avformat_alloc_output_context2(&_formatCtx, nullptr, nullptr, src.c_str()); @@ -77,6 +79,8 @@ namespace ffcpp { } MediaFile::~MediaFile() { + _streams.clear(); + if(_mode == Mode::Write) { avformat_free_context(_formatCtx); } else { @@ -100,6 +104,18 @@ namespace ffcpp { return _streams.back(); } + Packet MediaFile::readPacket() { + AVPacket packet; + packet.data = nullptr; + packet.size = 0; + int res = av_read_frame(_formatCtx, &packet); + return Packet(packet); + } + + AVMediaType MediaFile::packetType(const Packet &packet) { + return _formatCtx->streams[packet.streamIndex()]->codec->codec_type; + } + void MediaFile::writeHeader() { int res = avformat_write_header(_formatCtx, nullptr); throwIfError(res, "error writing header"); @@ -109,4 +125,10 @@ namespace ffcpp { int res = av_write_trailer(_formatCtx); throwIfError(res, "error writing trailer"); } + + void MediaFile::writePacket(Packet &packet) { + int res = av_interleaved_write_frame(_formatCtx, packet); + throwIfError(res, "cannot write frame to output file"); + } + } diff --git a/ffcpp/MediaFile.h b/ffcpp/MediaFile.h index 46f7682..d7c82d9 100644 --- a/ffcpp/MediaFile.h +++ b/ffcpp/MediaFile.h @@ -34,10 +34,12 @@ namespace ffcpp { Stream& videoStream(size_t index = 0); Stream& audioStream(size_t index = 0); Stream& addStream(AVCodecID codecID, int width, int height, AVPixelFormat pixelFormat); - + Packet readPacket(); + AVMediaType packetType(const Packet& packet); void writeHeader(); void writeTrailer(); + void writePacket(Packet& packet); ~MediaFile(); diff --git a/ffcpp/Packet.cpp b/ffcpp/Packet.cpp index 2f07ebc..3120e74 100644 --- a/ffcpp/Packet.cpp +++ b/ffcpp/Packet.cpp @@ -1,10 +1,53 @@ #include "Packet.h" +#include namespace ffcpp { Packet::Packet() { _packet.data = nullptr; _packet.size = 0; + av_init_packet(&_packet); + } + + Packet::Packet(AVPacket packet): _packet(packet) { + + } + + Packet::Packet(Packet&& packet) { + *this = std::move(packet); + } + + Packet& Packet::operator=(Packet&& packet) { + _packet = packet._packet; + packet._packet.size = 0; + packet._packet.data = nullptr; + return *this; + } + + Packet::~Packet() { + if(_packet.data) { + av_free_packet(&_packet); + } + } + + Packet::operator bool() { + return (_packet.data != nullptr); + } + + Packet::operator AVPacket*() { + return &_packet; + } + + int Packet::streamIndex() const { + return _packet.stream_index; + } + + void Packet::setStreamIndex(int index) { + _packet.stream_index = index; + } + + void Packet::rescaleTimestamps(AVRational from, AVRational to) { + av_packet_rescale_ts(&_packet, from, to); } } diff --git a/ffcpp/Packet.h b/ffcpp/Packet.h index 1c5fe6a..0c1f0d4 100644 --- a/ffcpp/Packet.h +++ b/ffcpp/Packet.h @@ -13,6 +13,18 @@ namespace ffcpp { public: Packet(); + Packet(AVPacket packet); + Packet(Packet&& packet); + Packet& operator=(Packet&& packet); + ~Packet(); + + operator bool(); + operator AVPacket*(); + + int streamIndex() const; + void setStreamIndex(int index); + + void rescaleTimestamps(AVRational from, AVRational to); }; } diff --git a/ffcpp/Stream.cpp b/ffcpp/Stream.cpp index 45aa1a2..8fd5524 100644 --- a/ffcpp/Stream.cpp +++ b/ffcpp/Stream.cpp @@ -4,7 +4,7 @@ namespace ffcpp { Stream::Stream(): _stream(nullptr) { - + int x = 0; } Stream::Stream(AVStream *stream): _stream(stream), _codec(_stream->codec, CodecType::Decoder) { diff --git a/main.cpp b/main.cpp index b90dcef..ef26e19 100644 --- a/main.cpp +++ b/main.cpp @@ -1,23 +1,25 @@ #include #include "ffcpp/ffcpp.h" #include "ffcpp/MediaFile.h" -#include "ffcpp/Stream.h" -#include "ffcpp/Codec.h" -extern "C" { - #include -} - -void checkResult(int res, const char* msg) { - if(res < 0) { - char errStr[260]; - av_strerror(res, errStr, 260); - std::cerr << msg << ": " << errStr << std::endl; - } -} +constexpr int VIDEO_STREAM_INDEX = 0; +constexpr int AUDIO_STREAM_INDEX = 1; namespace ff = ffcpp; +void flushEncoder(ff::MediaFile& file, ff::Codec& encoder, const ff::Stream& inStream, const ff::Stream& outStream, int streamIndex) { + if(encoder.capabilities() & AV_CODEC_CAP_DELAY) { + while (1) { + auto packet = encoder.encode(nullptr); + if(!packet) break; + + packet.setStreamIndex(streamIndex); + packet.rescaleTimestamps(inStream.timeBase(), outStream.timeBase()); + file.writePacket(packet); + } + } +} + int main(int argc, char** argv) { ff::init(); ff::MediaFile input(argv[1], ff::Mode::Read); @@ -31,94 +33,39 @@ int main(int argc, char** argv) { ff::Codec& vEncoder = outVStream.codec(); output.writeHeader(); - AVFrame* frame = nullptr; - int gotPicture = 0, gotPacket = 0, decodedFrames = 1; int64_t oldPts = 0, oldDts = 0; - while(true) { - AVPacket packet; - packet.data = nullptr; - packet.size = 0; - int res = av_read_frame(input, &packet); - if(res < 0) break; - - frame = av_frame_alloc(); - if(!frame) break; - - AVMediaType packetType = ((AVFormatContext*)input)->streams[packet.stream_index]->codec->codec_type; + while(auto packet = input.readPacket()) { + AVMediaType packetType = input.packetType(packet); if(packetType == AVMEDIA_TYPE_VIDEO) { - res = avcodec_decode_video2(vDecoder, frame, &gotPicture, &packet); - if(res < 0) { - av_frame_free(&frame); - break; + auto frame = vDecoder.decode(packet); + frame.setPictureType(AV_PICTURE_TYPE_NONE); + auto encPacket = vEncoder.encode(frame); + if(!encPacket) continue; + + encPacket.setStreamIndex(packetType == AVMEDIA_TYPE_VIDEO ? VIDEO_STREAM_INDEX : AUDIO_STREAM_INDEX); + + /* + // try to recover in case of bad pts/dts + if(encPacket.pts < encPacket.dts) { + encPacket.dts = encPacket.pts; } - if(gotPicture) { - frame->pts = av_frame_get_best_effort_timestamp(frame); - frame->pict_type = AV_PICTURE_TYPE_NONE; - //std::cout << "decoded frame: " << decodedFrames++ << " pts: " << frame->pts << ", dts: " << frame->pkt_dts << std::endl; + if(encPacket.pts < oldPts) + encPacket.pts = oldPts; - AVPacket encPacket; - encPacket.data = nullptr; - encPacket.size = 0; - av_init_packet(&encPacket); - encPacket.stream_index = 0; + if(encPacket.dts < oldDts) + encPacket.dts = oldDts; - res = avcodec_encode_video2(vEncoder, &encPacket, frame, &gotPacket); - av_frame_free(&frame); - if(res < 0) break; - if(!gotPacket) continue; + oldPts = encPacket.pts; + oldDts = encPacket.dts; + */ - // try to recover in case of bad pts/dts - if(encPacket.pts < encPacket.dts) { - encPacket.dts = encPacket.pts; - } - - if(encPacket.pts < oldPts) - encPacket.pts = oldPts; - - if(encPacket.dts < oldDts) - encPacket.dts = oldDts; - - oldPts = encPacket.pts; - oldDts = encPacket.dts; - - av_packet_rescale_ts(&encPacket, vStream.timeBase(), outVStream.timeBase()); - - res = av_interleaved_write_frame(output, &encPacket); - checkResult(res, "cannot write frame to output file"); - if(res < 0) break; - } + encPacket.rescaleTimestamps(vStream.timeBase(), outVStream.timeBase()); + output.writePacket(encPacket); } - - av_free_packet(&packet); } - // flush encoder - if(vEncoder.capabilities() & AV_CODEC_CAP_DELAY) { - std::cout << "flushing encoder" << std::endl; - int gotFrame = 0; - while (1) { - AVPacket encPacket; - encPacket.data = nullptr; - encPacket.size = 0; - av_init_packet(&encPacket); - encPacket.stream_index = 0; - int res = avcodec_encode_video2(vEncoder, &encPacket, nullptr, &gotFrame); - if (res < 0) { - std::cout << "avcodec_encode_video2 failed" << std::endl; - break; - } - if (gotFrame) { - //std::cout << "extra frame" << std::endl; - av_packet_rescale_ts(&encPacket, vStream.timeBase(), outVStream.timeBase()); - res = av_interleaved_write_frame(output, &encPacket); - checkResult(res, "[flush encoder] cannot write frame to output file"); - } else { - break; - } - } - } - + flushEncoder(output, vEncoder, vStream, outVStream, VIDEO_STREAM_INDEX); output.writeTrailer(); return 0;