最简单的基于FFMPEG的封装格式转换器(C++Qt 版)

Posted liyuanbhu

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了最简单的基于FFMPEG的封装格式转换器(C++Qt 版)相关的知识,希望对你有一定的参考价值。

最简单的基于FFMPEG的封装格式转换器(C++Qt 版)

这篇博客是我上篇博客的延续。建议大家先看看我上篇博客:

https://blog.csdn.net/liyuanbhu/article/details/121715005

之所以写这篇博客,是因为 ffmpeg 的 API 并不是那么友好。用面向对象的方式重新组织一下,代码会易读很多。下面先贴改写后的代码,大家可以比较一下,是否清爽了很多。

    QlyAVFormatContext inFile, outFile;
    inFile.openFile(QString("D:\\\\AV36_1.avi"));
    inFile.dumpFormat();
    QSet<AVMediaType> type;    type << AVMEDIA_TYPE_VIDEO << AVMEDIA_TYPE_AUDIO;
    QVector<QlyAVStream> inStreams = inFile.findStreams(type);

    outFile.createFile(QString(), QString("D:\\\\AV36_1-qt.mkv"));
    outFile.setStreams(inStreams);
    outFile.writeHeader();

    QlyAVPacket pkt;
    while(inFile.readFrame(pkt, type))
    
        AVRational in_tb = inFile.rawStream(pkt.streamIndex())->time_base;
        outFile.writeFrame(pkt, in_tb, true);
        pkt.unref();
    
    outFile.writeTrailer();

这个代码基本不需要解释。QlyAVFormatContext 封装了 AVFormatContext 。QlyAVStream 封装了 AVStream。QlyAVPacket 封装了 AVPacket。

inFile.openFile(QString("D:\\\\AV36_1.avi"));

这么简单一句就包含了原来的好几行代码:

int QlyAVFormatContext::openFile(QString url)

    m_url = url;
    isOutput = false;
    if(pFormatCtx) avformat_free_context(pFormatCtx);
    pFormatCtx = avformat_alloc_context();
    errorcode = avformat_open_input(&pFormatCtx, url.toLocal8Bit().constData(), nullptr, nullptr);
    if (errorcode < 0) return errorcode;
    errorcode = avformat_find_stream_info(pFormatCtx, nullptr);
    return errorcode;

并且由于C++ 有析构函数,所以释放资源的活可以自动完成。不需要我们自己费心:

QlyAVFormatContext::~QlyAVFormatContext()

    if(isOutput && pFormatCtx)
    
        closeOutputFile();
    
    else if(pFormatCtx)
    
        avformat_close_input(&pFormatCtx);
    
    avformat_free_context(pFormatCtx);

输出文件中添加 stream 也变得很简单:

QSet<AVMediaType> type;    type << AVMEDIA_TYPE_VIDEO << AVMEDIA_TYPE_AUDIO;
QVector<QlyAVStream> inStreams = inFile.findStreams(type);
outFile.setStreams(inStreams);

这里只提取了 音频流和视频流,忽略了其他的流。之后写文件头和文件尾还需要我们明确的写出来。这里应该还有改进的余地。下一版会进一步改进,把这两条语句也省了。

outFile.writeHeader();
// 这里是写数据帧的代码
outFile.writeTrailer();

最后是重点,从原始文件中读取数据,写到输出文件中。

QlyAVPacket pkt;
while(inFile.readFrame(pkt, type))

    AVRational in_tb = inFile.rawStream(pkt.streamIndex())->time_base;
    outFile.writeFrame(pkt, in_tb, true);
    pkt.unref();

这一块不太满意的地方是还要 pkt.unref() 。有可能可以在 readFrame 时自动的先 unref 这个 packet。

下面给出各个类的完整代码,首先是 QlyAVPacket:

#ifndef QLYAVPACKET_H
#define QLYAVPACKET_H

extern "C" 
#include "libavformat/avformat.h"
#include "libavcodec/avcodec.h"


class QlyAVPacket

public:
    QlyAVPacket();
    ~QlyAVPacket();
    unsigned int streamIndex() return m_packet.stream_index;
    void setStreamIndex(unsigned int index);
    void changeTimeBase(AVRational bq, AVRational cq, enum AVRounding rnd);
    void unref();
    AVPacket *ptr() return &m_packet;
    AVPacket m_packet;
    AVRational m_timeBase;
;

#endif // QLYAVPACKET_H

#include "QlyAVPacket.h"

QlyAVPacket::QlyAVPacket()




QlyAVPacket::~QlyAVPacket()

    av_packet_unref(&m_packet);


void QlyAVPacket::unref()

    av_packet_unref(&m_packet);


void QlyAVPacket::changeTimeBase(AVRational bq, AVRational cq, enum AVRounding rnd)

    m_packet.pts = av_rescale_q_rnd(m_packet.pts, bq, cq, rnd);
    m_packet.dts = av_rescale_q_rnd(m_packet.dts, bq, cq, rnd);
    m_packet.duration = av_rescale_q(m_packet.duration, bq, cq);
    m_packet.pos = -1;


void QlyAVPacket::setStreamIndex(unsigned int index)

    m_packet.stream_index = index;


之后是 QlyAVStream:

#ifndef QLYAVSTREAM_H
#define QLYAVSTREAM_H
#include<QtGlobal>

extern "C" 
#include "libavformat/avformat.h"
#include "libavcodec/avcodec.h"


class QlyAVStream

    friend class QlyAVFormatContext;
public:
    QlyAVStream() = default;
    QlyAVStream(AVStream *stream, int inStreamIndex = -1, int outStreamIndex = -1);
    QlyAVStream(const QlyAVStream &source);
    QlyAVStream & operator= (const QlyAVStream & source);
    AVCodecParameters * codecpar() return m_stream->codecpar;

    AVStream *m_stream;
    int m_inStreamIndex; //在 AVFormatContext 中的 index
    int m_outStreamIndex;
;

Q_DECLARE_TYPEINFO(QlyAVStream,
                   Q_PRIMITIVE_TYPE);


#endif // QLYAVSTREAM_H

#include "QlyAVStream.h"

QlyAVStream::QlyAVStream(AVStream *stream, int inStreamIndex, int outStreamIndex)

    m_stream = stream;
    m_inStreamIndex = inStreamIndex;
    m_outStreamIndex = outStreamIndex;


QlyAVStream::QlyAVStream(const QlyAVStream &source)

    m_stream = source.m_stream;
    m_inStreamIndex = source.m_inStreamIndex;
    m_outStreamIndex = source.m_outStreamIndex;


QlyAVStream & QlyAVStream::operator= (const QlyAVStream & source)

    m_stream = source.m_stream;
    m_inStreamIndex = source.m_inStreamIndex;
    m_outStreamIndex = source.m_outStreamIndex;
    return (*this);


最后是最重要的部分 QlyAVFormatContext:

#ifndef QLYAVFORMAT_H
#define QLYAVFORMAT_H
#include <QDebug>
#include <QString>
#include <QMap>
#include <QVector>
#include <QList>

extern "C" 
#include "libavformat/avformat.h"
#include "libavcodec/avcodec.h"


#include "QlyAVPacket.h"
#include "QlyAVStream.h"



class QlyAVStream;
class QlyAVFormatContext

public:
    QlyAVFormatContext();
    ~QlyAVFormatContext();
    static QString license();
    static QString version() ;
    static QString configuration();

    void dumpFormat();

    QVector<QlyAVStream> allStreams();
    QVector<QlyAVStream> findStreams(QSet<AVMediaType> mediaType);
    void setStreams(QVector<QlyAVStream> streams);

    AVStream * rawStream(int i);
    int outStreamIndex(QlyAVPacket &pack);
    bool readFrame(QlyAVPacket &pack, int streamIndex);
    bool readFrame(QlyAVPacket &pack, QSet<AVMediaType> type);
    bool writeFrame(QlyAVPacket &pack, AVRational timeBase, bool useStreamMap);
    int streamCount() const;
    QMap<QString, QString> metadata();

    int openFile(QString url);
    int createFile(QString formatName, QString fileName);
    void closeInputFile();
    int closeOutputFile();
    int writeHeader();
    int writeTrailer();

    QString errorString() const;

private:
    AVFormatContext *pFormatCtx;
    QMap<unsigned int, unsigned int> streamMap;// 记录了 in_stream 到 out_stream 的映射关系
    QString m_url;
    int errorcode;
    int isOutput;
    char errstr[512];
    AVRounding m_rounding;
;

QDebug operator<<(QDebug dbg, enum AVMediaType codec_type);
QDebug operator<<(QDebug dbg, AVRational r);

#endif // QLYAVFORMAT_H

#include "QlyAVFormatContext.h"
#include <QlyAVStream.h>

void msg(const char * str, int ret)

    static char err[512];
    if(ret < 0)
    
        av_strerror(ret, err, 512);
        qWarning() << str << " error:" << err;
        exit(ret);
    
    else
    
        qDebug() << str << " : success";
    


QDebug operator<<(QDebug dbg, enum AVMediaType codec_type)

    switch (codec_type) 
    case AVMEDIA_TYPE_UNKNOWN:
        dbg.nospace() << "AVMEDIA_TYPE_UNKNOWN";
        break;
    case AVMEDIA_TYPE_VIDEO:
        dbg.nospace() << "AVMEDIA_TYPE_VIDEO";
        break;
    case AVMEDIA_TYPE_AUDIO:
        dbg.nospace() << "AVMEDIA_TYPE_AUDIO";
        break;
    case AVMEDIA_TYPE_DATA:
        dbg.nospace() << "AVMEDIA_TYPE_DATA";
        break;
    case AVMEDIA_TYPE_SUBTITLE:
        dbg.nospace() << "AVMEDIA_TYPE_SUBTITLE";
        break;
    case AVMEDIA_TYPE_ATTACHMENT:
        dbg.nospace() << "AVMEDIA_TYPE_ATTACHMENT";
        break;
    case AVMEDIA_TYPE_NB:
        dbg.nospace() << "AVMEDIA_TYPE_NB";
        break;
    
    return dbg.maybeSpace();



QDebug operator<<(QDebug dbg, AVRational r)

    dbg.nospace() << r.num << "/" << r.den;
    return dbg.maybeSpace();


QlyAVFormatContext::QlyAVFormatContext()
    :pFormatCtx(nullptr),
      isOutput(false),
      m_rounding((AVRounding)(AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX))

    //av_register_all();
    //avformat_network_init();


void QlyAVFormatContext::dumpFormat()

    av_dump_format(pFormatCtx, 0, m_url.toLocal8Bit().constData(), isOutput);


QlyAVFormatContext::~QlyAVFormatContext()

    if(isOutput && pFormatCtx)
    
        closeOutputFile();
    
    else if(pFormatCtx)
    
        avformat_close_input(&pFormatCtx);
    
    avformat_free_context(pFormatCtx);


int QlyAVFormatContext::writeTrailer()

    errorcode = av_write_trailer(pFormatCtx);
    msg("QlyAVFormatContext::writeTrailer av_write_trailer", errorcode);
    return errorcode;


int QlyAVFormatContext::writeHeader()

    qDebug() << "in QlyAVFormatContext::writeHeader()";
    if (!(pFormatCtx->flags & AVFMT_NOFILE))
    
        errorcode = avio_open(&pFormatCtx->pb, m_url.toLocal8Bit().constData(), AVIO_FLAG_WRITE);
        msg("QlyAVFormatContext::writeTrailer avio_open", errorcode);
    
    errorcode = avformat_write_header(pFormatCtx, nullptr);
    msg("QlyAVFormatContext::writeHeader avformat_write_header", errorcode);

    return errorcode;


int QlyAVFormatContext::outStreamIndex(QlyAVPacket &pack)

    unsigned int index = pack.m_packet.stream_index;
    if(!streamMap.contains(index)) return -1;
    return streamMap[index];


bool QlyAVFormatContext::readFrame(QlyAVPacket &pack, QSet<AVMediaType> type)

    if(!pFormatCtx)
    
        errorcode = 0;
        return false;
    
    AVPacket * pPacket = pack.ptr();
    while(1)
    
        errorcode = av_read_frame(pFormatCtx, pPacket);
        if(errorcode < 0) return false;
        AVStream *in_stream = pFormatCtx->streams[pPacket->stream_index];
        AVCodecParameters *in_codecpar = in_stream->codecpar;

        if (!type.contains(in_codecpar->codec_type))
        
            av_packet_unref(pPacket);
            continue;
        
        return true;
    


bool QlyAVFormatContext::readFrame(QlyAVPacket &pack, int streamIndex)

    if(!pFormatCtx)
    
        errorcode = 0;
        return false;
    
    AVPacket * pPacket = pack.ptr();
    while(1)
    
        errorcode = av_read_frame(pFormatCtx, pPacket);
        qDebug() << "av_read_frame:errorcode = " << errorcode;
        if(errorcode < 0) return false;
        if (pPacket->stream_index != streamIndex || streamIndex != -1)
        
            av_packet_unref(pPacket);
            continue;
        
        qDebug() << "find a Packet";
        return true;
    


QString QlyAVFormatContext::license()

    return QString(avformat_license());


QMap<QString, QString> QlyAVFormatContext::metadata()

    QMap<QString, QString> data;
    AVDictionaryEntry *tag = nullptr;
    while ((tag = av_dict_get(pFormatCtx->metadata, "", tag, AV_DICT_IGNORE_SUFFIX)))
    
        data.insert(QString(tag->key), QString(tag->value));
    
    return data;


int QlyAVFormatContext::createFile(QString formatName, QString fileName)

    m_url = fileName;
    isOutput = true;
    if(pFormatCtx) avformat_free_context(pFormatCtx);
    //pFormatCtx = avformat_alloc_context();
    if(formatName.isNull() || formatName.isEmpty())
    
        errorcode = avformat_alloc_output_context2(&pFormatCtx, nullptr,
                                                   nullptr,
                                                   fileName.toLocal8Bit().constData());
    
    else
    
        errorcode = avformat_alloc_output_context2(&pFormatCtx, nullptr,
                                                   formatName.toLocal8Bit().constData(),
                                                   fileName.toLocal8Bit().constData());
    

    msg("QlyAVFormatContext::createFile avformat_alloc_output_context2", errorcode);
    return errorcode;


int QlyAVFormatContext::closeOutputFile()

    /* close output */
    if (pFormatCtx && !(pFormatCtx->flags & AVFMT_NOFILE))
    
        errorcode = avio_closep(&pFormatCtx->pb);
    
    return errorcode;


QVector<QlyAVStream> QlyAVFormatContext::allStreams()

    QVector<QlyAVStream> list;
    for (unsigned int i = 0; i < pFormatCtx->nb_streams; i++)
    
        AVStream *in_stream = pFormatCtx->streams[i];
        list << QlyAVStream(in_stream, i, -1);
    
    return list;


bool QlyAVFormatContext::writeFrame(QlyAVPacket &pack, AVRational timeBase, bool useStreamMap)

    AVPacket *pPacket = pack.ptr();
    if(useStreamMap && streamMap.contains(pPacket->stream_index))
    
        pPacket->stream_index = streamMap[pPacket->stream_index];
    
    AVRational out_timebase = pFormatCtx->streams[pPacket->stream_index]->time_base;

    pPacket->pts = av_rescale_q_rnd(pPacket->pts, timeBase, out_timebase, m_rounding);
    pPacket->dts = av_rescale_q_rnd(pPacket->dts, timeBase, out_timebase, m_rounding);
    pPacket->duration = av_rescale_q(pPacket->duration, timeBase, out_timebase);
    pPacket->pos = -1;

    errorcode = av_interleaved_write_frame(pFormatCtx, pPacket);
    //msg("QlyAVFormatContext::writeFrame av_interleaved_write_frame", errorcode);
    return (errorcode == 0);


QVector<QlyAVStream> QlyAVFormatContext::findStreams(QSet<AVMediaType> mediaType)

    QVector<QlyAVStream> list;
    for (unsigned int i = 0; i < pFormatCtx->nb_streams; i++)
    
        AVStream *in_stream = pFormatCtx->streams[i];
        AVCodecParameters *in_codecpar = in_stream->codecpar;
        if(mediaType.contains(in_codecpar->codec_type))
        
            qDebug() << "in_stream->index = " << in_stream->index;
            list << QlyAVStream(in_stream, i, -1);
        
    
    return list;


void QlyAVFormatContext::setStreams(QVector<QlyAVStream> streams)

    streamMap.clear();
    unsigned int stream_index = 0;
    QVectorIterator<QlyAVStream> iter(streams);
    while(iter.hasNext())
    
        QlyAVStream st = iter.next();
        AVStream * out_stream = avformat_new_stream(pFormatCtx, nullptr);
        avcodec_parameters_copy(out_stream->codecpar, st.codecpar());
        out_stream->codecpar->codec_tag = 0;
        streamMap[st.m_inStreamIndex] = stream_index;
        qDebug() << "streamMap[" << st.m_inStreamIndex << "] = " << stream_index;
        stream_index ++;
    
    qDebug() << "QlyAVFormatContext::setStreams success, stream_index = " << stream_index - 1;


AVStream * QlyAVFormatContext::rawStream(int i)

    if(pFormatCtx) return pFormatCtx->streams[i];
    return 0;


int QlyAVFormatContext::streamCount() const

    if(pFormatCtx) return pFormatCtx->nb_streams;
    return 0;


QString QlyAVFormatContext::version()

    return QString(avformat_version());


QString QlyAVFormatContext::configuration()

    return QString(avformat_configuration());


void QlyAVFormatContext::closeInputFile()

    if(pFormatCtx)
        avformat_close_input(&pFormatCtx);


int QlyAVFormatContext::openFile(QString url)

    m_url = url;
    isOutput = false;
    if(pFormatCtx) avformat_free_context(pFormatCtx);
    pFormatCtx = avformat_alloc_context();
    errorcode = avformat_open_input(&pFormatCtx, url.toLocal8Bit().constData(), nullptr, nullptr);
    if (errorcode < 0) return errorcode;
    errorcode = avformat_find_stream_info(pFormatCtx, nullptr);
    return errorcode;



QString QlyAVFormatContext::errorString() const

    char err[512];
    av_strerror(errorcode, err, 1024);
    return QString(err);


至此,这个代码就完整了。后面我会在这个代码的基础上继续增加新的功能。请大家关注我后面的博客。

以上是关于最简单的基于FFMPEG的封装格式转换器(C++Qt 版)的主要内容,如果未能解决你的问题,请参考以下文章

最简单的基于FFMPEG的封装格式转换器(致敬雷霄骅)

最简单的基于FFMPEG的封装格式转换器(致敬雷霄骅)

基于FFMPEG的封装格式转换器

FFMPEG 音频封装编码

音视频处理之FFmpeg封装格式20180510

最简单的基于FFmpeg的移动端例子:IOS 视频转码器