基于 FFMPEG 的视频编码 源码(libavcodec,C++ Qt)

Posted liyuanbhu

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了基于 FFMPEG 的视频编码 源码(libavcodec,C++ Qt)相关的知识,希望对你有一定的参考价值。

基于 FFMPEG 的视频编码 源码(libavcodec,C++ Qt)

昨晚把源代码好好整理了一下,加入了视频时间限制功能。源码放这里,大家随便用。
关于代码的解释可以看我另一篇博客:
基于 FFMPEG 的视频编码(libavcodec ,致敬雷霄骅)

首先是头文件:

/****************************************************************************
** file: VideoRecorder.h
** brief: 利用 ffmpeg 实现视频录制
** Copyright (C) LiYuan
** Author: LiYuan
** E-Mail: 18069211#qq(.)com
** Version 1.0.1
** Last modified: 2021.12.28
** Modified By: LiYuan
**
** Permission is hereby granted, free of charge, to any person obtaining a copy
** of this software and associated documentation files (the "Software"), to deal
** in the Software without restriction, including without limitation the rights
** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
** copies of the Software, and to permit persons to whom the Software is
** furnished to do so, subject to the following conditions:
**
** The above copyright notice and this permission notice shall be included in
** all copies or substantial portions of the Software.
**
** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
** THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
** THE SOFTWARE.
****************************************************************************/

#ifndef VIDEORECORDER_H
#define VIDEORECORDER_H

#include <QObject>
#include <QTime>
#include <QTimer>
#include <QSize>
#include <QImage>
#include <QQueue>

extern "C" 
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libswscale/swscale.h>
#include <libavutil/opt.h>
#include <libavutil/imgutils.h>


namespace Qly 

class VideoRecorder : public QObject

    Q_OBJECT
public:
    explicit VideoRecorder(QObject *parent = nullptr);
    ~VideoRecorder();

    void setMaxRecordTime(int ms) m_timeout = ms * 1000;
    /**
     * @brief setAVCodecID 设置编码类型。默认是 MPEG4
     * @param id
     */
    void setAVCodecID(AVCodecID id);

    /**
     * @brief setTimeBase 设置视频文件的time base, 默认是 1/1000。 也就是 1ms 为基本单位。
     * @param timebase
     */
    void setTimeBase(AVRational timebase) m_time_base = timebase;

    /**
     * @brief openFile 建立视频文件
     * @param url
     * @return
     */
    int openFile(QString url);


    int errorcode() const return m_errorcode;

public slots:
    /**
    * @brief close 关闭视频文件
    * @return
    */
    bool close();
    /**
     * @brief setImage 将图像插入到视频中,以当前时间自动计算 pts
     * @param image
     * @return
     */
    bool setImage(const QImage &image);
    /**
     * @brief setImage 将图像插入到视频中
     * @param image
     * @param pts 时间戳,以 time base 为基本单位。第一张图像默认 pts 为 0。 如果 pts = -1 则根据当前时间自动计算 pts.
     * @return
     */
    bool setImage(const QImage &image, int pts);

private slots:

    void timeout();

protected:
    int writeHeader();
    int writeTrailer();
    bool writeFrame(const AVFrame *m_pFrame);
    int initFile(AVCodecID codecID, QSize size);
    void initStreamParameters(AVStream *stream);
    void buildFrameFromImage(AVFrame *m_pFrame, const QImage &image, int pts);

    AVFormatContext *m_pFormatCtx = nullptr;
    const AVCodec *m_pCodec = nullptr;
    AVCodecContext *m_pCodecCtx = nullptr;
    AVFrame *m_pFrame = nullptr;
    AVPacket *m_pPacket = nullptr;

    AVCodecID m_codecID = AV_CODEC_ID_MPEG4;
    AVPixelFormat m_format = AV_PIX_FMT_YUV420P;
    AVRational m_time_base = 1, 1000;

    int64_t m_bit_rate = 10000000;
    int m_width = 0;
    int m_height = 0;
    QString m_url;
private:
    int m_errorcode = 0;
    bool m_recording = false;
    QTime m_startTime;
    QTimer m_timer;
    int m_timeout = 1000 * 1800; //默认半个小时
;




#endif // VIDEORECORDER_H

实现代码如下:

/****************************************************************************
** file: VideoRecorder.cpp
** brief: 利用 ffmpeg 实现视频录制
** Copyright (C) LiYuan
** Author: LiYuan
** E-Mail: 18069211#qq(.)com
** Version 1.0.1
** Last modified: 2021.12.28
** Modified By: LiYuan
**
** Permission is hereby granted, free of charge, to any person obtaining a copy
** of this software and associated documentation files (the "Software"), to deal
** in the Software without restriction, including without limitation the rights
** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
** copies of the Software, and to permit persons to whom the Software is
** furnished to do so, subject to the following conditions:
**
** The above copyright notice and this permission notice shall be included in
** all copies or substantial portions of the Software.
**
** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
** THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
** THE SOFTWARE.
****************************************************************************/

#include <QDebug>
#include "VideoRecorder.h"

namespace Qly 

static enum AVPixelFormat toAVPixelFormat(QImage::Format format)

    switch (format) 
    case QImage::Format_Invalid:
        return AV_PIX_FMT_NONE;
    case QImage::Format_Mono:
        return AV_PIX_FMT_MONOBLACK;
    case QImage::Format_MonoLSB:
        return AV_PIX_FMT_NONE;
    case QImage::Format_Indexed8:
        return AV_PIX_FMT_PAL8;
    case QImage::Format_Alpha8:
    case QImage::Format_Grayscale8:
        return AV_PIX_FMT_GRAY8;
    case QImage::Format_Grayscale16:
        return AV_PIX_FMT_GRAY16LE;
    case QImage::Format_RGB32:
    case QImage::Format_ARGB32:
    case QImage::Format_ARGB32_Premultiplied:
        //return AV_PIX_FMT_BGRA;
        return AV_PIX_FMT_BGR0;
    case QImage::Format_RGB16:
    case QImage::Format_ARGB8565_Premultiplied:
        return AV_PIX_FMT_RGB565LE;
    case QImage::Format_RGB666:
    case QImage::Format_ARGB6666_Premultiplied:
        return AV_PIX_FMT_NONE;
    case QImage::Format_RGB555:
    case QImage::Format_ARGB8555_Premultiplied:
        return AV_PIX_FMT_BGR555LE;
    case QImage::Format_RGB888:
        return AV_PIX_FMT_RGB24;
    case QImage::Format_RGB444:
    case QImage::Format_ARGB4444_Premultiplied:
        return AV_PIX_FMT_RGB444LE;
    case QImage::Format_RGBX8888:
    case QImage::Format_RGBA8888:
    case QImage::Format_RGBA8888_Premultiplied:
        return AV_PIX_FMT_RGBA;
    case QImage::Format_BGR30:
    case QImage::Format_A2BGR30_Premultiplied:
    case QImage::Format_RGB30:
    case QImage::Format_A2RGB30_Premultiplied:
        return AV_PIX_FMT_NONE;
    case QImage::Format_RGBX64:
    case QImage::Format_RGBA64:
    case QImage::Format_RGBA64_Premultiplied:
        return AV_PIX_FMT_RGBA64LE;
    case QImage::Format_BGR888:
        return AV_PIX_FMT_BGR24;
    default:
        return AV_PIX_FMT_NONE;
    
    return AV_PIX_FMT_NONE;


VideoRecorder::VideoRecorder(QObject *parent) : QObject(parent)

    m_pFormatCtx = nullptr;
    m_pPacket = av_packet_alloc();
    if(!m_pPacket)
    
        qWarning() << "VideoRecorder::VideoRecorder av_packet_alloc failed.";
    
    m_pFrame = av_frame_alloc();
    if (!m_pFrame)
    
        qWarning() << "VideoRecorder::VideoRecorder av_frame_alloc failed.";
    


VideoRecorder::~VideoRecorder()

    avcodec_free_context(&m_pCodecCtx);
    av_frame_free(&m_pFrame);
    av_packet_free(&m_pPacket);
    if(m_pFormatCtx)
    
        avformat_free_context(m_pFormatCtx);
    


int VideoRecorder::openFile(QString url)

    m_startTime = QTime(); //将 m_startTime 复原到原始状态
    if(url.isNull() || url.isEmpty())
    
        qWarning() << "VideoRecorder::openFile failed, url is Invalid(empty)";
        return -1;
    
    m_url = url;
    if(m_pFormatCtx)
    
        avformat_free_context(m_pFormatCtx);
    
    m_errorcode = avformat_alloc_output_context2(&m_pFormatCtx, nullptr,
                                               nullptr,
                                               url.toLocal8Bit().constData());
    if(m_errorcode < 0)
    
        qWarning() << "In VideoRecorder::openFile avformat_alloc_output_context2 failed";
        return -2;
    
    qDebug() << "avformat_alloc_output_context2 success";
    if (!(m_pFormatCtx->flags & AVFMT_NOFILE))
    
        m_errorcode = avio_open(&m_pFormatCtx->pb, m_url.toLocal8Bit().constData(), AVIO_FLAG_READ_WRITE);
        if(m_errorcode < 0)
        
            qWarning() << "in VideoRecorder::openFile avio_open failed";
            return -3;
        
    
    qDebug() << "avio_open success";
    m_recording = true;
    return 0;


void VideoRecorder::initStreamParameters(AVStream * stream)

    stream->time_base.den = m_time_base.den;
    stream->time_base.num = m_time_base.num;
    stream->id = m_pFormatCtx->nb_streams -1;
    stream->index = m_pFormatCtx->nb_streams -1;
    stream->codecpar->codec_tag = 0;
    stream->codecpar->codec_type = m_pCodec->type;
    stream->codecpar->codec_id = m_pCodec->id;
    stream->codecpar->format = m_format;
    stream->codecpar->width = m_width;
    stream->codecpar->height = m_height;
    stream->codecpar->bit_rate = m_bit_rate;


int VideoRecorder::initFile(AVCodecID codecID, QSize size)

    qDebug() << "IN VideoRecorder::initFile";
    m_width = size.width();
    m_height = size.height();
    m_codecID = codecID;
    m_pCodec = avcodec_find_encoder(codecID);

    if (!m_pCodec)
    
        qWarning() << "VideoRecorder::initFile avcodec_find_encoder failed.";
        return -2;
    
    qDebug() << "avcodec_find_encoder success, codecID = " << codecID ;
    AVStream *pStream = avformat_new_stream(m_pFormatCtx, m_pCodec);
    if(pStream == nullptr)
    
        qWarning() << "VideoRecorder::initFile avformat_new_stream failed.";
        return -3;
    
    qDebug() << "avformat_new_stream success";

    initStreamParameters(pStream);
    //m_pCodecCtx = pStream->codec;

    qDebug() << "initStreamParameters success";
    if(m_pCodecCtx)
    
        qDebug() << "avcodec_free_context";
        avcodec_free_context(&m_pCodecCtx);
    

    qDebug() << "m_pCodecCtx = " << m_pCodecCtx;
    m_pCodecCtx = avcodec_alloc_context3(m_pCodec);
    if(!m_pCodecCtx)
    
       qWarning() << "VideoRecorder::initFile avcodec_alloc_context3 failed.";
       return -4;
    
    qDebug() << "avcodec_alloc_context3 success";
    m_pCodecCtx->codec_id = m_pCodec->id;
    m_pCodecCtx->time_base = pStream->time_base;
    m_pCodecCtx->gop_size = 10;
    m_pCodecCtx->max_b_frames = 0;

    //qDebug() << "max_b_frames";

    if (codecID == AV_CODEC_ID_H264)
    
     av_opt_set(m_pCodecCtx->priv_data, "preset", "fast", 0);
     //av_opt_set(pCodecCtx->priv_data, "tune", "zerolatency", 0);
     //av_opt_set(pCodecCtx->priv_data, "profile", "main", 0);

    
    else if(codecID == AV_CODEC_ID_H265)
    
     av_opt_set(m_pCodecCtx->priv_data, "preset", "fast", 0);
     //av_opt_set(pCodecCtx->priv_data, "tune", "zerolatency", 0);
     //av_opt_set(pCodecCtx->priv_data, "profile", "main", 0);
    

    qDebug() << "av_opt_set";
    /* Some formats want stream headers to be separate. */
    if (m_pFormatCtx->oformat->flags & AVFMT_GLOBALHEADER)
    
     m_pFormatCtx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
    

    avcodec_parameters_to_context(m_pCodecCtx, pStream->codecpar);
    m_errorcode = avcodec_open2(m_pCodecCtx, m_pCodec, nullptr);
    if(m_errorcode < 0)
    
        qWarning() << "VideoRecorder::initFile avcodec_open2 failed.";
        return -5;
    
    qDebug() << "avcodec_open2 success";
    m_pFrame->format = (int)m_pCodecCtx->pix_fmt;
    m_pFrame->width  = m_pCodecCtx->width;
    m_pFrame->height = m_pCodecCtx->height;

    if( av_frame_get_buffer(m_pFrame, 0) < 0 )
    
        qWarning() << "VideoRecorder::initFile av_frame_get_buffer() failed.";
        return -6;
    
     qDebug() << "av_frame_get_buffer success";
    return 0;


void VideoRecorder::buildFrameFromImage(AVFrame *pFrame, const QImage &image, int pts)

    //qDebug() << "IN VideoRecorder::buildFrameFromImage";
    /* make sure the frame data is writable */
    if (av_frame_make_writable(pFrame) < 0)
    
        qWarning() << "in VideoRecorder::buildFrameFromImage av_frame_make_writable(pFrame) failed";
        return;
    

    int width = image.width();
    int height = image.height();
    AVPixelFormat imgFmt = toAVPixelFormat(image.format());
    SwsContext * pContext = sws_getContext(width, height, imgFmt,
                                           width, height, (AVPixelFormat)pFrame->format, SWS_POINT, nullptr, nullptr, nullptr);
    if(!pContext) return;

    const uint8_t *in_data[1];
    int in_linesize[1];

    in_data[0] = image.bits();
    in_linesize[0] = image.bytesPerLine();

    sws_scale(pContext, in_data, in_linesize, 0, height,
              pFrame->data, pFrame->linesize);
    sws_freeContext(pContext);

    pFrame->pts = pts;


bool VideoRecorder::setImage(const QImage &image, int pts)

    //qDebug() << "IN VideoRecorder::setImage";
    if(!m_recording)
    
        qDebug() << "in VideoRecorder::setImage m_recording = false";
        return false;
    
    QTime t = QTime::currentTime();
    if( pts < 0 ) // 说明这时要用真实的时间来做为 pts
    
        if(m_startTime.isNull())
        
            m_startTime = t; // 说明这是第一帧。需要初始化起始时间。
        
        int oldpts = m_startTime.msecsTo(t);

        pts = av_rescale_q_rnd(oldpts, AVRational(1, 1000), m_time_base, AV_ROUND_NEAR_INF);
        //qDebug() << "oldpts = " << oldpts << ", pts = " << pts;
    
    //qDebug() << "pts = " << pts;
    if(m_width == 0) // 说明这是第一个帧
    
        initFile(m_codecID, image.size());
        writeHeader();
        av_dump_format(m_pFormatCtx, 0, m_url.toLocal8Bit().constData(), true);
        m_timer.singleShot(m_timeout, this, SLOT(timeout()));
    
    buildFrameFromImage(m_pFrame, image, pts);
    return writeFrame(m_pFrame);


void VideoRecorder::timeout()

    close()

以上是关于基于 FFMPEG 的视频编码 源码(libavcodec,C++ Qt)的主要内容,如果未能解决你的问题,请参考以下文章

基于 FFMPEG 的视频编码 源码(libavcodec,C++ Qt)

ffmpeg4 代码输出mp4文件帧数

基于 FFMPEG 的视频编码(libavcodec ,致敬雷霄骅)

基于 FFMPEG 的视频编码(libavcodec ,致敬雷霄骅)

基于 FFMPEG 的视频编码(libavcodec ,致敬雷霄骅)

Android 音视频深入 十五 FFmpeg 实现基于Rtmp协议的推流(附源码下载)