From d7f8c733ec35fb1c951444052e6901dcd36788ff Mon Sep 17 00:00:00 2001 From: Selim Mustafaev Date: Sun, 28 Aug 2016 23:29:54 +0300 Subject: [PATCH] added some C++ wrappers --- CMakeLists.txt | 2 +- ffcpp/Codec.cpp | 72 +++++++++++++++++++++++++++++ ffcpp/Codec.h | 41 +++++++++++++++++ ffcpp/MediaFile.cpp | 107 ++++++++++++++++++++++++++++++++++++++++++ ffcpp/MediaFile.h | 47 +++++++++++++++++++ ffcpp/Stream.cpp | 36 +++++++++++++++ ffcpp/Stream.h | 30 ++++++++++++ ffcpp/ffcpp.cpp | 22 +++++++++ ffcpp/ffcpp.h | 13 ++++++ main.cpp | 110 +++++++++++--------------------------------- 10 files changed, 397 insertions(+), 83 deletions(-) create mode 100644 ffcpp/Codec.cpp create mode 100644 ffcpp/Codec.h create mode 100644 ffcpp/MediaFile.cpp create mode 100644 ffcpp/MediaFile.h create mode 100644 ffcpp/Stream.cpp create mode 100644 ffcpp/Stream.h create mode 100644 ffcpp/ffcpp.cpp create mode 100644 ffcpp/ffcpp.h diff --git a/CMakeLists.txt b/CMakeLists.txt index ba021ed..5013326 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,6 +11,6 @@ link_directories(${FFMPEG_LIBRARY_DIRS}) #message(FATAL_ERROR ${FFMPEG_LIBRARIES}) -set(SOURCE_FILES main.cpp) +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) 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 new file mode 100644 index 0000000..c6745a4 --- /dev/null +++ b/ffcpp/Codec.cpp @@ -0,0 +1,72 @@ +#include "Codec.h" +#include "ffcpp.h" +#include + +namespace ffcpp { + + Codec::Codec(): _codecCtx(nullptr), _codec(nullptr) { + + } + + Codec::Codec(AVCodecContext *ctx, CodecType type): _codecCtx(ctx) { + if(type == CodecType::Encoder) { + _codec = avcodec_find_encoder(ctx->codec_id); + } else { + _codec = avcodec_find_decoder(ctx->codec_id); + } + + if(!_codec) throw std::runtime_error("cannot find codec"); + + int res = avcodec_open2(_codecCtx, _codec, nullptr); + throwIfError(res, "cannot open codec"); + } + + Codec::Codec(AVCodecContext *ctx, AVCodec *codec) { + _codecCtx = ctx; + _codec = codec; + + int res = avcodec_open2(_codecCtx, _codec, nullptr); + throwIfError(res, "cannot open codec"); + } + + Codec::~Codec() { + //avcodec_close(_codecCtx); + } + + Codec::operator AVCodecContext*() const { + return _codecCtx; + } + + int Codec::width() const { + return _codecCtx->width; + } + + int Codec::height() const { + return _codecCtx->height; + } + + AVRational Codec::timeBase() const { + return _codecCtx->time_base; + } + + int Codec::capabilities() const { + return _codec->capabilities; + } + + AVPixelFormat Codec::pixelFormat() const { + return _codecCtx->pix_fmt; + } + + void Codec::setWidth(int width) { + _codecCtx->width = width; + } + + void Codec::setHeight(int height) { + _codecCtx->height = height; + } + + void Codec::setPixelFormat(AVPixelFormat pixelFormat) { + _codecCtx->pix_fmt = pixelFormat; + } + +} diff --git a/ffcpp/Codec.h b/ffcpp/Codec.h new file mode 100644 index 0000000..ba8e45a --- /dev/null +++ b/ffcpp/Codec.h @@ -0,0 +1,41 @@ +#ifndef FFCONV_CODEC_H +#define FFCONV_CODEC_H + +extern "C" { + #include +} + +namespace ffcpp { + + enum class CodecType { + Encoder, + Decoder + }; + + class Codec { + private: + AVCodecContext* _codecCtx; + AVCodec* _codec; + + public: + Codec(); + Codec(AVCodecContext* ctx, CodecType type); + Codec(AVCodecContext* ctx, AVCodec* codec); + ~Codec(); + + operator AVCodecContext*() const; + + int width() const; + int height() const; + AVRational timeBase() const; + int capabilities() const; + AVPixelFormat pixelFormat() const; + + void setWidth(int width); + void setHeight(int height); + void setPixelFormat(AVPixelFormat pixelFormat); + }; + +} + +#endif //FFCONV_CODEC_H diff --git a/ffcpp/MediaFile.cpp b/ffcpp/MediaFile.cpp new file mode 100644 index 0000000..6201113 --- /dev/null +++ b/ffcpp/MediaFile.cpp @@ -0,0 +1,107 @@ +#include "MediaFile.h" +#include "ffcpp.h" +#include + +namespace ffcpp { + + MediaFile::MediaFile(const std::string& src, Mode mode) { + _mode = mode; + if(mode == Mode::Read) { + int res = avformat_open_input(&_formatCtx, src.c_str(), nullptr, nullptr); + throwIfError(res, "cannot open input file"); + + res = avformat_find_stream_info(_formatCtx, nullptr); + throwIfError(res, "cannot find stream info"); + } else if(mode == Mode::Write) { + int res = avformat_alloc_output_context2(&_formatCtx, nullptr, nullptr, src.c_str()); + throwIfError(res, "cannot allocate format context"); + + if (!(_formatCtx->oformat->flags & AVFMT_NOFILE)) { + res = avio_open(&_formatCtx->pb, src.c_str(), AVIO_FLAG_WRITE); + throwIfError(res, "cannot init avio context"); + } + + if(_formatCtx->oformat->flags & AVFMT_GLOBALHEADER) + _formatCtx->flags |= CODEC_FLAG_GLOBAL_HEADER; + + av_dump_format(_formatCtx, 0, src.c_str(), 1); + } + } + + bool MediaFile::hasStream(AVMediaType type) const { + for(size_t i = 0; i < _formatCtx->nb_streams; ++i) { + if(_formatCtx->streams[i]->codec->codec_type == type) { + return true; + } + } + + return false; + } + + bool MediaFile::hasVideo() const { + return hasStream(AVMEDIA_TYPE_VIDEO); + } + + bool MediaFile::hasAudio() const { + return hasStream(AVMEDIA_TYPE_AUDIO); + } + + Stream MediaFile::getStream(AVMediaType type, size_t index) const { + for(size_t i = 0, curIndex = 0; i < _formatCtx->nb_streams; ++i) { + if(_formatCtx->streams[i]->codec->codec_type == type) { + if(curIndex == index) { + return Stream(_formatCtx->streams[i]); + } else { + curIndex++; + } + } + } + + throw std::runtime_error("cannot find stream"); + } + + Stream MediaFile::videoStream(size_t index /* = 0 */) const { + return getStream(AVMEDIA_TYPE_VIDEO, index); + } + + Stream MediaFile::audioStream(size_t index /* = 0 */) const { + return getStream(AVMEDIA_TYPE_AUDIO, index); + } + + MediaFile::operator AVFormatContext*() const { + return _formatCtx; + } + + MediaFile::~MediaFile() { + if(_mode == Mode::Write) { + avformat_free_context(_formatCtx); + } else { + avformat_close_input(&_formatCtx); + } + } + + Stream MediaFile::addStream(AVCodecID codecID, int width, int height, AVPixelFormat pixelFormat) { + AVCodec* codec = avcodec_find_encoder(codecID); + if(!codec) throw std::runtime_error("cannot find codec"); + + AVStream* stream = avformat_new_stream(_formatCtx, codec); + if(!stream) throw std::runtime_error("cannot create stream"); + + AVCodecContext* ctx = stream->codec; + ctx->width = width; + ctx->height = height; + ctx->pix_fmt = pixelFormat; + + return Stream(stream, codec); + } + + void MediaFile::writeHeader() { + int res = avformat_write_header(_formatCtx, nullptr); + throwIfError(res, "error writing header"); + } + + void MediaFile::writeTrailer() { + int res = av_write_trailer(_formatCtx); + throwIfError(res, "error writing trailer"); + } +} diff --git a/ffcpp/MediaFile.h b/ffcpp/MediaFile.h new file mode 100644 index 0000000..33763cf --- /dev/null +++ b/ffcpp/MediaFile.h @@ -0,0 +1,47 @@ +#ifndef FFCONV_MEDIAFILE_H +#define FFCONV_MEDIAFILE_H + +#include "Stream.h" + +extern "C" { + #include +} + +#include + +namespace ffcpp { + + enum class Mode { + Read, + Write + }; + + class MediaFile { + private: + AVFormatContext* _formatCtx; + Mode _mode; + + public: + MediaFile(const std::string& src, Mode mode); + + operator AVFormatContext*() const; + + bool hasVideo() const; + bool hasAudio() const; + Stream videoStream(size_t index = 0) const; + Stream audioStream(size_t index = 0) const; + Stream addStream(AVCodecID codecID, int width, int height, AVPixelFormat pixelFormat); + + void writeHeader(); + void writeTrailer(); + + ~MediaFile(); + + private: + bool hasStream(AVMediaType type) const; + Stream getStream(AVMediaType type, size_t index) const; + }; + +} + +#endif //FFCONV_MEDIAFILE_H diff --git a/ffcpp/Stream.cpp b/ffcpp/Stream.cpp new file mode 100644 index 0000000..29dbdbc --- /dev/null +++ b/ffcpp/Stream.cpp @@ -0,0 +1,36 @@ +#include "Stream.h" +#include + +namespace ffcpp { + + Stream::Stream() { + _stream = nullptr; + } + + Stream::Stream(AVStream *stream) { + _stream = stream; + _codec = Codec(_stream->codec, CodecType::Decoder); + } + + Stream::Stream(AVStream *stream, AVCodec* encoder) { + _stream = stream; + _codec = Codec(stream->codec, encoder); + } + + Stream::operator AVStream*() const { + return _stream; + } + + Codec Stream::codec() const { + return _codec; + } + + AVRational Stream::timeBase() const { + return _stream->time_base; + } + + void Stream::setTimeBase(AVRational timeBase) { + _stream->time_base = timeBase; + } + +} diff --git a/ffcpp/Stream.h b/ffcpp/Stream.h new file mode 100644 index 0000000..f99d5ab --- /dev/null +++ b/ffcpp/Stream.h @@ -0,0 +1,30 @@ +#ifndef FFCONV_STREAM_H +#define FFCONV_STREAM_H + +#include "Codec.h" + +extern "C" { + #include +} + +namespace ffcpp { + + class Stream { + private: + AVStream* _stream; + Codec _codec; + + public: + Stream(); + Stream(AVStream* stream); + Stream(AVStream* stream, AVCodec* encoder); + operator AVStream*() const; + Codec codec() const; + + AVRational timeBase() const; + void setTimeBase(AVRational timeBase); + }; + +} + +#endif //FFCONV_STREAM_H diff --git a/ffcpp/ffcpp.cpp b/ffcpp/ffcpp.cpp new file mode 100644 index 0000000..5845f72 --- /dev/null +++ b/ffcpp/ffcpp.cpp @@ -0,0 +1,22 @@ +#include "ffcpp.h" +#include + +extern "C" { + #include +} + +namespace ffcpp { + + void init() { + av_register_all(); + } + + void throwIfError(int result, const std::string& description) { + if(result < 0) { + char errStr[260]; + av_strerror(result, errStr, 260); + throw std::runtime_error(description + ": " + errStr); + } + } + +} \ No newline at end of file diff --git a/ffcpp/ffcpp.h b/ffcpp/ffcpp.h new file mode 100644 index 0000000..731c920 --- /dev/null +++ b/ffcpp/ffcpp.h @@ -0,0 +1,13 @@ +#ifndef FFCONV_FFCPP_H +#define FFCONV_FFCPP_H + +#include + +namespace ffcpp { + + void init(); + void throwIfError(int result, const std::string& description); + +} + +#endif //FFCONV_FFCPP_H diff --git a/main.cpp b/main.cpp index 87300ee..55ff8ad 100644 --- a/main.cpp +++ b/main.cpp @@ -1,4 +1,8 @@ #include +#include "ffcpp/ffcpp.h" +#include "ffcpp/MediaFile.h" +#include "ffcpp/Stream.h" +#include "ffcpp/Codec.h" extern "C" { #include @@ -12,69 +16,23 @@ void checkResult(int res, const char* msg) { } } +namespace ff = ffcpp; + int main(int argc, char** argv) { - AVFormatContext* inFmtCtx = nullptr; - AVFormatContext* outFmtCtx = nullptr; + ff::init(); + ff::MediaFile input(argv[1], ff::Mode::Read); + ff::MediaFile output(argv[2], ff::Mode::Write); - av_register_all(); - int res = avformat_open_input(&inFmtCtx, argv[1], nullptr, nullptr); - checkResult(res, "cannot open input"); - - res = avformat_find_stream_info(inFmtCtx, nullptr); - checkResult(res, "cannot find stream info"); - - res = avformat_alloc_output_context2(&outFmtCtx, nullptr, nullptr, argv[2]); - checkResult(res, "cannot allocate output context"); - - AVStream *videoStream = nullptr, *audioStream = nullptr; - for(int i = 0; i < inFmtCtx->nb_streams; ++i) { - AVStream* stream = inFmtCtx->streams[i]; - if(stream->codec->codec_type == AVMEDIA_TYPE_VIDEO) { - std::cout << "found video stream " << avcodec_get_name(stream->codec->codec_id) << std::endl; - videoStream = stream; - } else if(stream->codec->codec_type == AVMEDIA_TYPE_AUDIO) { - std::cout << "found audio stream " << avcodec_get_name(stream->codec->codec_id) << std::endl; - audioStream = stream; - } + ff::Stream vStream; + if(input.hasVideo()) { + vStream = input.videoStream(); } - AVCodecContext* codecCtx = videoStream->codec; - AVCodec* codec = avcodec_find_decoder(codecCtx->codec_id); - if(!codec) std::cout << "cannot find decoder" << std::endl; - - res = avcodec_open2(codecCtx, codec, nullptr); - checkResult(res, "cannot open codec for decoding"); - - // coder - AVCodec* dstVCodec = avcodec_find_encoder(AV_CODEC_ID_H264); - if(!dstVCodec) std::cout << "cannot find encoder" << std::endl; - - AVStream* outVideoStream = avformat_new_stream(outFmtCtx, dstVCodec); - if(!outVideoStream) std::cout << "cannot create output video stream" << std::endl; - - if (!(outFmtCtx->oformat->flags & AVFMT_NOFILE)) { - res = avio_open(&outFmtCtx->pb, argv[2], AVIO_FLAG_WRITE); - checkResult(res, "cannot open output file"); - } - - av_dump_format(outFmtCtx, 0, argv[2], 1); - - AVCodecContext* dstVCodecCtx = outVideoStream->codec; - dstVCodecCtx->pix_fmt = AV_PIX_FMT_YUV420P; - dstVCodecCtx->width = codecCtx->width; - dstVCodecCtx->height = codecCtx->height; - //dstVCodecCtx->time_base = codecCtx->time_base; - outVideoStream->time_base = codecCtx->time_base; - outFmtCtx->duration = inFmtCtx->duration; - - if(outFmtCtx->oformat->flags & AVFMT_GLOBALHEADER) - dstVCodecCtx->flags |= CODEC_FLAG_GLOBAL_HEADER; - - res = avcodec_open2(dstVCodecCtx, dstVCodec, nullptr); - checkResult(res, "cannot open codec for encoding"); - - res = avformat_write_header(outFmtCtx, nullptr); - checkResult(res, "error writing header to output file"); + ff::Codec vDecoder = vStream.codec(); + ff::Stream outVStream = output.addStream(AV_CODEC_ID_H264, vDecoder.width(), vDecoder.height(), AV_PIX_FMT_YUV420P); + outVStream.setTimeBase(vDecoder.timeBase()); + ff::Codec vEncoder = outVStream.codec(); + output.writeHeader(); AVFrame* frame = nullptr; int gotPicture = 0, gotPacket = 0, decodedFrames = 1; @@ -83,15 +41,15 @@ int main(int argc, char** argv) { AVPacket packet; packet.data = nullptr; packet.size = 0; - res = av_read_frame(inFmtCtx, &packet); + int res = av_read_frame(input, &packet); if(res < 0) break; frame = av_frame_alloc(); if(!frame) break; - AVMediaType packetType = inFmtCtx->streams[packet.stream_index]->codec->codec_type; + AVMediaType packetType = ((AVFormatContext*)input)->streams[packet.stream_index]->codec->codec_type; if(packetType == AVMEDIA_TYPE_VIDEO) { - res = avcodec_decode_video2(codecCtx, frame, &gotPicture, &packet); + res = avcodec_decode_video2(vDecoder, frame, &gotPicture, &packet); if(res < 0) { av_frame_free(&frame); break; @@ -108,7 +66,7 @@ int main(int argc, char** argv) { av_init_packet(&encPacket); encPacket.stream_index = 0; - res = avcodec_encode_video2(dstVCodecCtx, &encPacket, frame, &gotPacket); + res = avcodec_encode_video2(vEncoder, &encPacket, frame, &gotPacket); av_frame_free(&frame); if(res < 0) break; if(!gotPacket) continue; @@ -127,9 +85,9 @@ int main(int argc, char** argv) { oldPts = encPacket.pts; oldDts = encPacket.dts; - av_packet_rescale_ts(&encPacket, videoStream->time_base, outVideoStream->time_base); + av_packet_rescale_ts(&encPacket, vStream.timeBase(), outVStream.timeBase()); - res = av_interleaved_write_frame(outFmtCtx, &encPacket); + res = av_interleaved_write_frame(output, &encPacket); checkResult(res, "cannot write frame to output file"); if(res < 0) break; } @@ -139,7 +97,7 @@ int main(int argc, char** argv) { } // flush encoder - if(dstVCodecCtx->codec->capabilities & AV_CODEC_CAP_DELAY) { + if(vEncoder.capabilities() & AV_CODEC_CAP_DELAY) { std::cout << "flushing encoder" << std::endl; int gotFrame = 0; while (1) { @@ -148,15 +106,15 @@ int main(int argc, char** argv) { encPacket.size = 0; av_init_packet(&encPacket); encPacket.stream_index = 0; - res = avcodec_encode_video2(dstVCodecCtx, &encPacket, nullptr, &gotFrame); + 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, videoStream->time_base, outVideoStream->time_base); - res = av_interleaved_write_frame(outFmtCtx, &encPacket); + 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; @@ -164,19 +122,7 @@ int main(int argc, char** argv) { } } - res = av_write_trailer(outFmtCtx); - checkResult(res, "error writing trailer to output file"); - - if(codecCtx) - avcodec_close(codecCtx); - - if(dstVCodecCtx) - avcodec_close(dstVCodecCtx); - - if(inFmtCtx) - avformat_close_input(&inFmtCtx); - - avformat_free_context(outFmtCtx); + output.writeTrailer(); return 0; }