-
-
Save jimj316/931b8ccbbdbcb5b2d3337879b4829e25 to your computer and use it in GitHub Desktop.
Sample FFMpeg decoder
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <QDebug> | |
#include <QElapsedTimer> | |
#include <QFile> | |
#include <QTextStream> | |
extern "C" | |
{ | |
#include <libavcodec/avcodec.h> | |
#include <libavutil/common.h> | |
#include <libavutil/imgutils.h> | |
#include <libavutil/mathematics.h> | |
#include <libavformat/avformat.h> | |
} | |
#define SKIP_ERRORS | |
#define EMUL_MHD_FLAGS | |
QString error_string2(int error) | |
{ | |
char errbuf[1024]; | |
av_make_error_string(errbuf, 1024, error); | |
return QString(errbuf); | |
} | |
void SaveAvFrame2(AVFrame *avFrame, QString name) | |
{ | |
FILE *fDump = fopen((name).toUtf8().constData(), "ab"); | |
uint32_t pitchY = avFrame->linesize[0]; | |
uint32_t pitchU = avFrame->linesize[1]; | |
uint32_t pitchV = avFrame->linesize[2]; | |
uint8_t *avY = avFrame->data[0]; | |
uint8_t *avU = avFrame->data[1]; | |
uint8_t *avV = avFrame->data[2]; | |
for (uint32_t i = 0; i < (uint32_t) avFrame->height; i++) { | |
fwrite(avY, avFrame->width, 1, fDump); | |
avY += pitchY; | |
} | |
for (uint32_t i = 0; i < (uint32_t) avFrame->height/2; i++) { | |
fwrite(avU, avFrame->width/2, 1, fDump); | |
avU += pitchU; | |
} | |
for (uint32_t i = 0; i < (uint32_t) avFrame->height/2; i++) { | |
fwrite(avV, avFrame->width/2, 1, fDump); | |
avV += pitchV; | |
} | |
fclose(fDump); | |
} | |
bool ok = false; | |
int decode(QString inFilename, QString outFilename) | |
{ | |
AVFormatContext* formatContext; | |
int ret; | |
formatContext = avformat_alloc_context(); | |
ret = avformat_open_input(&formatContext, inFilename.toUtf8().data(), NULL, NULL); | |
if (ret < 0) | |
{ | |
qWarning()<<"avformat_open_input() error:" << error_string2(ret); | |
return 1; | |
} | |
int videoStream = av_find_best_stream(formatContext, AVMediaType::AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0); | |
AVCodecParameters* codecPar = formatContext->streams[videoStream] | |
->codecpar; | |
AVCodec* codec = avcodec_find_decoder(codecPar->codec_id); | |
if (codec) | |
{ | |
qDebug() << "Identified codec: " << codec->name; | |
} | |
else | |
{ | |
qWarning() << "Failed to identify codec!"; | |
return 1; | |
} | |
AVCodecContext* codecContext = avcodec_alloc_context3(codec); | |
if (!codecContext) | |
{ | |
qWarning() << "Failed to alloc codec context!"; | |
return 1; | |
} | |
ret = avcodec_parameters_to_context(codecContext, codecPar); | |
if (ret != 0) | |
{ | |
qWarning()<<"avcodec_parameters_to_context() error:" << error_string2(ret); | |
return 1; | |
} | |
/* | |
codecContext->bits_per_raw_sample = 8; | |
codecContext->level=40; | |
codecContext->pix_fmt = AV_PIX_FMT_YUV420P; | |
codecContext->profile=578;*/ | |
if (codec->capabilities & AV_CODEC_CAP_FRAME_THREADS) | |
qDebug() << "Frame-level multithreading supported."; | |
if (codec->capabilities & AV_CODEC_CAP_SLICE_THREADS) | |
qDebug() << "Slice-level multithreading supported."; | |
if (codec->capabilities & AV_CODEC_CAP_HYBRID) | |
qDebug() << "Hardware support: hybrid"; | |
else if (codec->capabilities & AV_CODEC_CAP_HARDWARE) | |
qDebug() << "Hardware support: yes"; | |
else | |
qDebug() << "Hardware support: no"; | |
#ifdef EMUL_MHD_FLAGS | |
if (codec->capabilities & AV_CODEC_CAP_AUTO_THREADS) | |
{ | |
qDebug() << "Using 0 (auto) threads"; | |
codecContext->thread_count = 0; | |
} | |
else | |
{ | |
codecContext->thread_count = 16; | |
qDebug() << "Using"<<codecContext->thread_count<<"threads"; | |
} | |
codecContext->thread_type = FF_THREAD_SLICE; // NB: frame-level threading is useless here | |
codecContext->thread_safe_callbacks = true; | |
// codecContext->lowres = 1; // NB: not supported on VP9 | |
codecContext->flags |= AV_CODEC_FLAG_LOW_DELAY; | |
codecContext->flags2 |= AV_CODEC_FLAG2_FAST; | |
codecContext->skip_loop_filter = AVDISCARD_DEFAULT; // NB: changing this makes no difference | |
codecContext->skip_frame = AVDISCARD_DEFAULT; // | |
codecContext->skip_idct = AVDISCARD_DEFAULT; // NB: changing this makes no difference | |
// codecContext->err_recognition = AV_EF_IGNORE_ERR; | |
// codecContext->error_concealment = FF_EC_DEBLOCK; | |
#endif | |
ret = avcodec_open2(codecContext, codec, NULL); | |
if (ret != 0) | |
{ | |
qWarning()<<"avcodec_open2() error:" << error_string2(ret); | |
return 1; | |
} | |
QElapsedTimer timer = QElapsedTimer(); | |
timer.start(); | |
QString filename="Profile.txt"; | |
QFile file( filename ); | |
if ( !file.open(QIODevice::ReadWrite) ) | |
{ | |
qWarning() << "Couldn't open profile output stream."; | |
return 1; | |
} | |
QTextStream stream( &file ); | |
// double duration = formatContext->duration / ((double)AV_TIME_BASE); | |
// int lastSec = 0; | |
AVPacket packet; // un-decoded data read from the file, to be sent to libav | |
AVFrame* frame; // decoded frame received from libav | |
bool shouldContinue = false; // whether we should keep looping | |
bool shouldReceive = false; // whether we should try to get a decoded frame from libav next pass | |
bool shouldRead = true; // whether we should feed a new packet to decode to libav next pass | |
do | |
{ | |
stream << timer.elapsed() << ":" << "Starting new frame.\n"; | |
if (shouldRead) | |
{ | |
ret = av_read_frame(formatContext, &packet); | |
stream << timer.elapsed() << ":" << "av_read_frame()\n"; | |
} | |
shouldRead = true; | |
if (ret != 0) | |
{ | |
shouldContinue = false; | |
if (ret == AVERROR_EOF) | |
qDebug() << "av_read_frame() reports EOF."; | |
else | |
qWarning()<<"av_read_frame() error:" << error_string2(ret); | |
} | |
else | |
{ | |
shouldContinue = true; | |
if (packet.stream_index == videoStream) | |
{ | |
ret = avcodec_send_packet(codecContext, &packet); | |
stream << timer.elapsed() << ":" << "avcodec_send_packet()\n"; | |
if (ret != 0) | |
{ | |
if (ret == AVERROR(EAGAIN)) | |
{ | |
qDebug()<<"avcodec_send_packet(): Must receive again before sending this packet"; | |
shouldContinue = true; | |
shouldReceive = true; | |
shouldRead = false; // skip the next read | |
} | |
#ifdef SKIP_ERRORS | |
else if (ret == AVERROR_INVALIDDATA) | |
{ | |
qWarning()<<"avcodec_send_packet(): video error detected, skipping!"; | |
shouldReceive = false; | |
} | |
#endif | |
else | |
{ | |
qWarning()<<"avcodec_send_packet() error:" << error_string2(ret); | |
shouldContinue = false; | |
shouldReceive = false; | |
} | |
} | |
else | |
shouldReceive = true; | |
if (shouldReceive) | |
{ | |
frame = av_frame_alloc(); | |
ret = avcodec_receive_frame(codecContext, frame); | |
stream << timer.elapsed() << ":" << "avcodec_receive_frame()\n"; | |
if (ret != 0) | |
{ | |
if (ret == AVERROR(EAGAIN)) | |
{ | |
qWarning()<<"avcodec_receive_frame(): Must send packet again before receiving frame"; | |
} | |
else | |
{ | |
qWarning()<<"avcodec_receive_frame() error:" << error_string2(ret); | |
shouldContinue = false; | |
} | |
} | |
// export every decoded frame as a YUV file | |
else if (!outFilename.isEmpty()) | |
{ | |
SaveAvFrame2(frame, outFilename); | |
stream << timer.elapsed() << ":" << "SaveAvFrame2()\n"; | |
} | |
av_frame_free(&frame); | |
} | |
} | |
} | |
stream << timer.elapsed() << ":" << "Completed frame.\n"; | |
} | |
while(shouldContinue); | |
qDebug() << "Done, took"<<timer.elapsed()<<"ms."; | |
avformat_close_input(&formatContext); | |
return 0; | |
} | |
// args: (input-file-name) (output-file-name) | |
int main(int argc, char *argv[]) | |
{ | |
QString inFilename; | |
QString outFilename; | |
if (argc >= 2) | |
inFilename = QString(argv[1]); | |
else | |
inFilename = "/path/to/test_file.mkv"; | |
if (argc >= 3) | |
outFilename = QString(argv[2]); | |
else | |
outFilename = ""; | |
qDebug() << "Input: " << inFilename; | |
qDebug() << "Output:" << outFilename; | |
return decode(inFilename, outFilename); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment