h264解码保存为yuv格式「建议收藏」

h264解码保存为yuv格式「建议收藏」h264解码保存为yuv格式使用ffmpeg库从摄像头读取h264数据进行解码和显示,最后保存为yuv格式的文件,开发环境为QTCreater。程序流程图如下图所示:ffmpeg.h头文件如下:#ifndefFFMPEG_H#defineFFMPEG_H#include<QMainWindow>#include<QMutex>#include&l…

大家好,又见面了,我是你们的朋友全栈君。

h264解码保存为yuv格式

使用ffmpeg库从摄像头读取h264数据进行解码和显示,最后保存为yuv格式的文件,开发环境为QTCreater。程序流程图如下图所示:
在这里插入图片描述
为了使解码过程不占用主界面显示资源以及方便后续代码开发移植,此处设计了一个用于解码和保存的类,解码和保存yuv文件单独开一个线程在后台执行。其中引入的ffmpeg的头文件非全部必须,ffmpeg.h头文件如下:

#ifndef FFMPEG_H
#define FFMPEG_H

#include <QMainWindow>
#include <QMutex>
#include <QDateTime>
#include <QFile>
#include <QThread>
#include <QDebug>

//引入ffmpeg头文件
extern "C" {
#include "libavutil/opt.h"
#include "libavutil/time.h"
#include "libavutil/frame.h"
#include "libavutil/pixdesc.h"
#include "libavutil/avassert.h"
#include "libavutil/imgutils.h"
#include "libavutil/ffversion.h"
#include "libavcodec/avcodec.h"
#include "libswscale/swscale.h"
#include "libavdevice/avdevice.h"
#include "libavformat/avformat.h"
#include "libavfilter/avfilter.h"

#ifndef gcc45
#include "libavutil/hwcontext.h"
#endif
}


namespace Ui {
class ffmpeg;
}

class ffmpeg : public QThread
{
    Q_OBJECT

public:
    explicit ffmpeg(QWidget *parent = nullptr);
    ~ffmpeg();

protected:
    void run();
signals:
    //收到图片信号
    void receiveImage(const QImage &image);

private:

    uint64_t framIndex;
    int lastMsec;
    int videoStreamIndex;               //视频流索引
    int videoWidth;                     //视频宽度
    int videoHeight;                    //视频高度
    int videoFps;                       //视频流帧率
    int frameFinish;                    //一帧完成
    bool saveFile;
    bool isOutputFileOpen;
    uint64_t framCount;                 //帧计数
    uint8_t *buffer;                    //存储解码后图片buffer
    AVOutputFormat *ofmt = NULL;        //输出格式

    AVPacket *avDePacket;               //解码包对象

    AVFrame *avDeFrameYuv;              //解码帧对象YUV
    AVFrame *avDeFrameRgb;              //解码帧对象RGB

    AVFormatContext *ifmt_ctx;          //输入封装格式对象
    AVFormatContext *ofmt_ctx;          //输出封装格式对象

    AVStream *in_stream;                //输入视频流
    AVStream *out_stream;               //输出视频流

    AVCodecContext *deCodecCtx;         //解码器上下文

    SwsContext *swsContextYuvtoRgb;     //格式转换上下文(YuvtoRgb)

    int oldWidth;                       //上一次视频宽度
    int oldHeight;                      //上一次视频高度

    const char *outputFilename = "ffmpegVideo.yuv";
    const char *inputFilename = "/dev/video1";

    QFile  outFile;private:
    Ui::ffmpeg *ui;
    int initDecodeVideo();
    int playVideo();

};

#endif // FFMPEG_H

解码和保存的类的实现ffmpeg.c文件如下:

#include "ffmpeg.h"

#define TIMEMS      qPrintable(QTime::currentTime().toString("HH:mm:ss zzz"))

ffmpeg::ffmpeg(QWidget *parent) :
    QThread(parent)
{
    framCount = 0;
    frameFinish = 0;
    saveFile = true;
    framIndex = 0;
    isOutputFileOpen = false;
    initDecodeVideo();
}


ffmpeg::~ffmpeg()
{
}


/* 功能:初始化解封装上下文,解码器上下文,和格式转换上下文(yuv转rgb)
 *      1 解封装
 *      2 解码
 *      3 格式转换
 * 参数:无
 * 返回值:成功返回零,失败返回-1
 */
int ffmpeg::initDecodeVideo()
{
    //注册库中所有可用的文件格式和解码器
    av_register_all();
    //注册所有设备,主要用于本地摄像机播放支持
    avdevice_register_all();

    qDebug() << TIMEMS << "init ffmpeg lib ok" << " version:" << FFMPEG_VERSION;


    AVDictionary *options = NULL;
    AVCodec *deCodec = NULL;       //解码器

    av_dict_set_int(&options, "rtbufsize", 18432000  , 0);

    //为解封装上下文开辟空间
    ifmt_ctx = avformat_alloc_context();
    //解封装对象
    AVInputFormat *ifmt = av_find_input_format("video4linux2");

    //打开输入视频流,进行解封装
    int result = avformat_open_input(&ifmt_ctx, inputFilename, ifmt, &options);
    if (result < 0) {
        qDebug() << TIMEMS << "open input error" << inputFilename;
        return false;
    }
    //释放设置参数
    if(options != NULL) {
        av_dict_free(&options);
    }

    //获取流信息
    result = avformat_find_stream_info(ifmt_ctx, NULL);
    if (result < 0) {
        qDebug() << TIMEMS << "find stream info error";
        return false;
    }
    videoStreamIndex = -1;

    videoStreamIndex = av_find_best_stream(ifmt_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, &deCodec, 0);

    if (videoStreamIndex < 0) {
        qDebug() << TIMEMS << "find video stream index error";
        return false;
    }

    //从输入封装上下文获取输入视频流
    in_stream = ifmt_ctx->streams[videoStreamIndex];
    if (!in_stream)
    {
        printf("Failed get input stream\n");
        return false;
    }

    //获取视频流解码器上下文
    deCodecCtx = in_stream->codec;

    //获取分辨率大小
    videoWidth = in_stream->codec->width;
    videoHeight = in_stream->codec->height;

    //如果没有获取到宽高则返回
    if (videoWidth == 0 || videoHeight == 0) {
        qDebug() << TIMEMS << "find width height error";
        return false;
    }

    //获取视频流的帧率 fps,要对0进行过滤,除数不能为0,有些时候获取到的是0
    int num = in_stream->codec->framerate.num;
    int den = in_stream->codec->framerate.den;
    if (num != 0 && den != 0) {
        videoFps = num / den ;
    }

    QString videoInfo = QString("视频流信息 -> 索引: %1   格式: %2  时长: %3 秒  fps: %4  分辨率: %5*%6")
                        .arg(videoStreamIndex).arg(ifmt_ctx->iformat->name)
                        .arg((ifmt_ctx->duration) / 1000000).arg(videoFps).arg(videoWidth).arg(videoHeight);
    qDebug() << TIMEMS << videoInfo;

    //打开视频解码器
    result = avcodec_open2(deCodecCtx, deCodec, NULL);
    if (result < 0) {
        qDebug() << TIMEMS << "open video codec error";
        return false;
    }

    avDePacket = av_packet_alloc();
    avDeFrameYuv = av_frame_alloc();
    avDeFrameRgb = av_frame_alloc();

    //比较上一次文件的宽度高度,当改变时,需要重新分配内存
    if (oldWidth != videoWidth || oldHeight != videoHeight) {
        int byte = avpicture_get_size(AV_PIX_FMT_RGB32, videoWidth, videoHeight);
        buffer = (uint8_t *)av_malloc(byte * sizeof(uint8_t));
        oldWidth = videoWidth;
        oldHeight = videoHeight;
    }

    //定义像素格式
    AVPixelFormat srcFormat = AV_PIX_FMT_YUV420P;
    AVPixelFormat dstFormat = AV_PIX_FMT_RGB32;

    av_image_fill_arrays(avDeFrameRgb->data, avDeFrameRgb->linesize, buffer, dstFormat, videoWidth, videoHeight, 1);
    int flags = SWS_FAST_BILINEAR;

    swsContextYuvtoRgb = sws_getContext(videoWidth, videoHeight, srcFormat, videoWidth, videoHeight, dstFormat, flags, NULL, NULL, NULL);

    //打开输出视频的文件
    outFile.setFileName(outputFilename);
    outFile.open(QIODevice::WriteOnly);
    qDebug() << TIMEMS << "init ffmpegVideo ok";
    return 0;
}

int ffmpeg::playVideo()
{
    while(true)
    {
        if (av_read_frame(ifmt_ctx, avDePacket) >= 0) {
            //判断当前包是视频还是音频
            int index = avDePacket->stream_index;
            in_stream  = ifmt_ctx->streams[index];

            if (index == videoStreamIndex) {
                avcodec_decode_video2(deCodecCtx, avDeFrameYuv, &frameFinish, avDePacket);
                if (frameFinish)
                {

                    //将数据转成一张图片YuvtoRgb
                    sws_scale(swsContextYuvtoRgb, (const uint8_t *const *)avDeFrameYuv->data, avDeFrameYuv->linesize,\
                              0, videoHeight, avDeFrameRgb->data, avDeFrameRgb->linesize);

                    QImage image((uchar *)buffer, videoWidth, videoHeight, QImage::Format_RGB32);

                    if (!image.isNull()) {
                        emit receiveImage(image);
                    }

                    framIndex ++;
                    qDebug()<< "解码到第" << framIndex << "帧";
                    qDebug() << TIMEMS;
                    if(framIndex > 200)
                    {
                        framIndex = 0;
                        break;
                    }

                    for(int i = 0;i < avDeFrameYuv->height;i++){
                        outFile.write((char *)(avDeFrameYuv->data[0] + i * avDeFrameYuv->linesize[0]),avDeFrameYuv->width);
                    }

                    int loop = avDeFrameYuv->height / 2;
                    int len_uv = avDeFrameYuv->width / 2;

                    for(int i = 0;i < loop;i++){
                        outFile.write((char *)(avDeFrameYuv->data[1] + i * avDeFrameYuv->linesize[1]),len_uv);
                    }
                    for(int i = 0;i < loop;i++){
                        outFile.write((char *)(avDeFrameYuv->data[2] + i * avDeFrameYuv->linesize[2]),len_uv);
                    }


                }
                av_packet_unref(avDePacket);
                av_freep(avDePacket);
            }

        }
    }
    outFile.close();

    avformat_free_context(ifmt_ctx);
    //关闭编码和解码器
    avcodec_close(deCodecCtx);
    //清理编码器和解码器上下文
    avcodec_free_context(&deCodecCtx);
    //清理格式转换上下文
    sws_freeContext(swsContextYuvtoRgb);

    qDebug() << TIMEMS << "stop ffmpeg thread";
}
void ffmpeg::run()
{
    playVideo();
}

主线程要实现解码操作需要创建一个ffmpeg实例,然后调用ffmpeg的start方法,此时会执行ffmpeg的run函数进行解码,解码线程将解码后的一帧图像传递给主线程,主线程将该图像进行播放显示,主线程代码如下:

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "ffmpeg.h"

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    ffmpeg * ffmpegThread = new ffmpeg(this);
    ffmpegThread->start();
    connect(ffmpegThread, SIGNAL(receiveImage(QImage)), this, SLOT(updateImage(QImage)));
}

MainWindow::~MainWindow()
{
    delete ui;
}

void MainWindow::updateImage(const QImage &image)
{
    ui->label->resize(image.width(),image.height());
    ui->label->setPixmap(QPixmap::fromImage(image));
}
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/148148.html原文链接:https://javaforall.net

(0)
上一篇 2022年6月15日 下午4:36
下一篇 2022年6月15日 下午4:46


相关推荐

  • PyCharm卸载教程(Windows10)

    PyCharm卸载教程(Windows10)本文总结了自身卸载 PyCharm 的经验 不想使用 PyCharm 或者想要卸载重新安装的同学可作参考 Windows10

    2026年3月27日
    2
  • 百度快照更新周期、百度收录更新时间[通俗易懂]

    百度快照更新周期、百度收录更新时间[通俗易懂]很多做SEO的都不清楚百度快照的更新周期,所以很多时候都不能有针对性的对网站进行操作,错过了很多机会。百度收录的更新日期一般是每个月的11号和26号,特别是26号,更新最大,但K站也是最多的。另外百度也有一个小的更新的日期,即每周四凌晨4点左右,对网站的访问量没有什么效果,只有到了中午的日期,百度对网站关键字的搜索停止重新调整之后,才会有访问量上的大的变化,有升有降。总体上来说是大致为一个

    2026年4月15日
    3
  • 目标检测使用LabelImg标注VOC数据格式和YOLO数据格式——LabelImg使用详细教程

    目标检测使用LabelImg标注VOC数据格式和YOLO数据格式——LabelImg使用详细教程欢迎大家关注笔者,你的关注是我持续更博的最大动力原创文章,转载告知,盗版必究@[toc](目标检测使用LabelImg标注VOC数据格式和YOLO数据格式——LabelImg使用详细教程文章目录:)1LabelImg介绍与安装1.1Label介绍github是目标检测数据标注工具,可以标注标注两种格式:VOC标签格式,标注的标签存储在xml文件YOLO标签格式,标注的标签存储在txt文件中LabelImg的github主页地址:点我,带你去>https://…

    2022年6月16日
    59
  • iptables 开放防火墙端口

    iptables 开放防火墙端口总所周知,远程访问centos服务的时候,都要开放相应服务的端口。下面就来说道说道。存在的问题一般情况下,centos下都会存在一个/etc/sysconfig/iptables文件,该文件是用来记录要开放的端口ip的。当然,也不排除新安装的centos,空空如也缺失的,此时就无法执行serviceiptablesrestart。如果你的centos中能正常运…

    2022年10月19日
    3
  • IDEA中配置Maven阿里云镜像

    IDEA中配置Maven阿里云镜像Maven 官方镜像在国内访问很慢 所以我们需要在 IDEA 中配置阿里云镜像

    2026年3月19日
    2
  • poe交换机的供电方式_交换机需要电源吗

    poe交换机的供电方式_交换机需要电源吗前言:近年来,PoE供电技术的发展势头越来越强劲。凭借简化用电设备的安装和部署、节能,安全等一系列优势,PoE供电成为无线覆盖、安防监控、以及智能电网等场景的新宠。在技术交流中,工程商困惑最多的其中就有POE供电的问题,本文就汇总了大家最关注的问题,集中解答。 ▶ 问题一:何为PoE技术? PoE(PowerOverEthernet)指的是在现有的以太网Cat.5布线基础…

    2026年4月16日
    6

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号