ffmpeg 图片推流

avencoder.h 

#ifndef AVENCODER_H
#define AVENCODER_H

#include 

extern "C" {
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libavdevice/avdevice.h"
#include "libswscale/swscale.h"

static void yuv422to420(AVFrame *, AVFrame *);
}

#include "cameramanager.h"

class AVEncoder : public QObject
{
    Q_OBJECT
public:
    explicit AVEncoder(QObject *parent = nullptr);
    explicit AVEncoder(const char * codec, AVFormatContext * dectx, AVCodecContext * pdeavcctx, const char * filename, CameraManager * cmThread);
    int encode(AVFrame * inputFrame);

public slots:
    void enc_work();


private:
    //char * filename;
    AVCodec * pencodeavc;
    AVCodecContext * pencodeavctx;
    AVCodec * pdecode;
    AVCodecContext * pdecavctx;
    AVFormatContext * pavctx;
    AVRational input_framerate;
    AVStream * input_stream;
    AVStream * output_stream;
    AVFormatContext * poutfctx;
    CameraManager * cmThread;
    int videoindex = 0;
    int vpts = 0;

signals:

};

#endif // AVENCODER_H

avencoder.c

 

#include "avencoder.h"
#include "unistd.h"

AVEncoder::AVEncoder(QObject *parent)
    : QObject{parent}
{
}
/*这个函数是AVEncoder类的构造函数,用于初始化编码器。具体的功能如下:
*将输入的解码器上下文、解码器上下文、相机管理器线程保存到成员变量中。
*遍历输入的格式上下文的流,查找视频流的索引。
*将视频流保存到成员变量中。
*使用av_guess_frame_rate函数获取输入视频流的帧率。
*分配输出格式上下文,并设置输出格式为"flv",同时传入输出文件名。
*创建输出流,并保存到成员变量中。
*根据编码器名字查找对应的编码器。
*分配编码器上下文,并保存到成员变量中。
*设置编码器的参数,包括预设参数、调整参数、高度、宽度、像素格式、GOP大小、最大B帧数和时间基准。
*如果输出格式要求全局头,设置编码器的标志为全局头。
*打开编码器。
*如果打开编码器失败,输出编码器名称。
*将编码器上下文的参数复制到输出流的解码器参数。
*打开输出文件。
*写入输出文件头部。
*如果写入文件头部失败,输出错误信息。
*总的来说,这个函数的作用是初始化编码器,并准备好输出文件。
*/
AVEncoder::AVEncoder(const char *codec, AVFormatContext *dectx, AVCodecContext *pdeavcctx, const char *filename, CameraManager *cmThread)
{
    int ret;
    this->pavctx = dectx; // 输入的解码器上下文
    this->pdecavctx = pdeavcctx; // 输入的解码器上下文
    this->cmThread = cmThread; // 相机管理器线程

    // 查找并设置视频流的索引
    for (unsigned int i = 0; i < dectx->nb_streams; i++) {
        if (dectx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
            videoindex = i;
            break;
        }
    }
    input_stream = dectx->streams[videoindex]; // 输入的视频流
    input_framerate = av_guess_frame_rate(dectx, input_stream, NULL); // 获取输入视频流的帧率
    // 分配输出格式上下文
    avformat_alloc_output_context2(&poutfctx, NULL, "flv", filename); // 创建输出格式上下文
    output_stream = avformat_new_stream(poutfctx, NULL); // 创建输出流
    this->pencodeavc = avcodec_find_encoder_by_name(codec); // 根据编码器名字查找对应的编码器
    this->pencodeavctx = avcodec_alloc_context3(this->pencodeavc); // 分配编码器上下文
    // 设置编码器参数
    av_opt_set(pencodeavctx->priv_data, "preset", "ultrafast", 0); // 设置编码器的预设参数为"ultrafast"
    av_opt_set(pencodeavctx->priv_data, "tune", "zerolatency", 0); // 设置编码器的调整参数为"zerolatency"
    pencodeavctx->height = pdeavcctx->height; // 设置编码器的高度
    pencodeavctx->width = pdeavcctx->width; // 设置编码器的宽度
    pencodeavctx->pix_fmt = AV_PIX_FMT_YUV420P; // 设置编码器的像素格式为YUV420P
    pencodeavctx->gop_size = 50; // 设置编码器的GOP大小
    pencodeavctx->max_b_frames = 0; // 设置编码器的最大B帧数
    pencodeavctx->time_base = av_inv_q(input_framerate); // 设置编码器的时间基准与输入视频流的帧率相反
    output_stream->time_base = pencodeavctx->time_base; // 设置输出流的时间基准与编码器的时间基准相同
    if (poutfctx->oformat->flags & AVFMT_GLOBALHEADER)
        pencodeavctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; // 如果输出格式要求全局头,则设置编码器的标志为全局头

    // 打开编码器
    ret = avcodec_open2(pencodeavctx, pencodeavc, NULL); // 打开编码器
    if (ret < 0) {
        qDebug() << this->pencodeavc->name << Qt::endl; // 输出编码器名称
    }
    avcodec_parameters_from_context(output_stream->codecpar, pencodeavctx); // 从编码器上下文复制参数到输出流的解码器参数
    // 打开输出文件
    avio_open(&poutfctx->pb, filename, AVIO_FLAG_WRITE); // 打开输出文件
    ret = avformat_write_header(poutfctx, NULL); // 写入输出文件头部
    if (ret < 0) {
        qDebug() << "AV WRITE HEAD ERROR" << Qt::endl; // 输出写入文件头部错误信息
    }
}

/*这个函数用于将输入的帧数据进行编码,并将编码后的数据包写入输出文件中。
*函数的参数是一个AVFrame类型的指针inFrame,表示输入的帧数据。
*函数首先分配一个AVPacket结构体outPacket,用于存储编码后的数据包。
*然后,函数分配一个AVFrame结构体yuvFrame,用于存储YUV格式的帧数据。如果分配失败,函数会打印错误信息。
*接下来,函数设置yuvFrame的像素格式、高度和宽度,并分配帧数据的内存缓冲区,要求内存对齐到32字节。如果分配内存失败,函数会打印错误信息。
*然后,函数调用yuv422to420函数将输入帧数据转换为YUV420P格式。
*接着,函数设置yuvFrame的展示时间戳,并将其发送给编码器进行编码。
*在一个while循环中,函数不断接收编码后的数据包,直到接收完所有的数据包或出现错误。如果接收数据包失败,函数会返回-1。
*对于每个接收到的数据包,函数设置数据包的流索引和位置,并将数据包的时间戳转换为输出流的时间基准。然后,函数将数据包写入输出文件。
*最后,函数清除输入帧数据、YUV帧数据和数据包的引用,并释放数据包的内存。
*函数返回值为0,表示编码成功。
*总的来说,这个函数的作用是将输入的帧数据进行编码,并将编码后的数据包写入输出文件
*/
int AVEncoder::encode(AVFrame * inFrame)
{
    int ret;
    AVPacket * outPacket = av_packet_alloc(); // 分配一个AVPacket结构体,用于存储编码后的数据包
    AVFrame * yuvFrame;
    yuvFrame = av_frame_alloc(); // 分配一个AVFrame结构体,用于存储YUV格式的帧数据
    if (yuvFrame == NULL) {
        qDebug() << "YUVRAME alloc failure" << Qt::endl; // 如果yuvFrame分配失败,打印错误信息
    }

    yuvFrame->format = AV_PIX_FMT_YUV420P; // 设置帧数据的像素格式为YUV420P
    yuvFrame->height = inFrame->height;    // 设置帧数据的高度
    yuvFrame->width = inFrame->width;      // 设置帧数据的宽度
    ret = av_frame_get_buffer(yuvFrame, 32); // 分配帧数据的内存缓冲区,要求内存对齐到32字节
    if (ret < 0) {
        char errstr[50] = "";
        av_strerror(ret, errstr, sizeof(errstr));
        qDebug() << errstr << Qt::endl;    // 如果分配内存失败,打印错误信息
        qDebug() << "av get buffer error" << Qt::endl;
    }

    // 转换图像格式为YUV420P
    yuv422to420(inFrame, yuvFrame); // 调用yuv422to420函数将输入帧数据转换为YUV420P格式

    yuvFrame->pts = vpts; // 设置帧数据的展示时间戳
    vpts++;

    // 发送YUV帧给编码器
    ret = avcodec_send_frame(pencodeavctx, yuvFrame); // 将YUV帧发送给编码器进行编码

    while (ret >= 0) {
        // 接收编码后的数据包
        ret = avcodec_receive_packet(pencodeavctx, outPacket); // 从编码器接收编码后的数据包
        if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
            break;
        }
        else if (ret < 0) {
            return -1; // 如果接收数据包失败,返回错误码-1
        }

        outPacket->stream_index = videoindex; // 设置数据包的流索引
        outPacket->pos = -1; // 设置数据包的位置为-1,表示位置未知
        av_packet_rescale_ts(outPacket, pencodeavctx->time_base, output_stream->time_base); // 将数据包的时间戳转换为输出流的时间基准

        // 将数据包写入输出文件
        ret = av_interleaved_write_frame(poutfctx, outPacket); // 将数据包写入输出文件
        if (ret < 0) {
            char errstr[50] = "";
            av_strerror(ret, errstr, sizeof(errstr));
            qDebug() << errstr << Qt::endl; // 如果写入数据包失败,打印错误信息
        }
    }

    av_frame_unref(inFrame); // 清除输入帧数据的引用
    av_frame_unref(yuvFrame); // 清除YUV帧数据的引用
    av_packet_unref(outPacket); // 清除数据包的引用
    av_packet_free(&outPacket); // 释放数据包的内存
    return 0;
}

/*这个函数的作用是进行编码工作。它在一个循环中运行,直到停止流媒体传输。在每次循环中,它会处理事件队列,以确保程序不会阻塞。*/
void AVEncoder::enc_work()
{
while (cmThread->getOnStreaming()) { // 循环直到停止流媒体传输
QCoreApplication::processEvents(); // 处理事件队列,保证程序不会阻塞

    if (cmThread->AVFrameQueue.empty()) { // 如果帧队列为空,则继续下一次循环
        continue;
    }
    AVFrame * frame = cmThread->AVFrameQueue.front(); // 获取帧队列中的第一个帧
    cmThread->AVFrameQueue.pop(); // 从帧队列中移除第一个帧
    encode(frame); // 调用encode函数对帧进行编码
}
av_write_trailer(poutfctx); // 将输出文件的尾部写入编码器
}

static void yuv422to420 (AVFrame * inframe, AVFrame * outframe) {
    SwsContext* conversion = sws_getContext(inframe->width, inframe->height, (AVPixelFormat) inframe->format, outframe->width, outframe->height, AVPixelFormat::AV_PIX_FMT_YUV420P, SWS_FAST_BILINEAR, NULL, NULL, NULL);
    sws_scale(conversion, inframe->data, inframe->linesize, 0, inframe->height, outframe->data, outframe->linesize);
    sws_freeContext(conversion);
}

你可能感兴趣的:(ffmpeg)