Skip to content

Instantly share code, notes, and snippets.

@jimj316

jimj316/main.cpp Secret

Created September 3, 2021 09:12
Show Gist options
  • Save jimj316/931b8ccbbdbcb5b2d3337879b4829e25 to your computer and use it in GitHub Desktop.
Save jimj316/931b8ccbbdbcb5b2d3337879b4829e25 to your computer and use it in GitHub Desktop.
Sample FFMpeg decoder
#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