八、ffmpeg录制视频为yuv文件

前言

测试环境:

  • ffmpeg的4.3.2自行编译版本
  • windows环境
  • qt5.12

图片的一些重要知识:

RGB图片

  • 位深度:每一个像素都会使用n个二进制位来存储颜色信息。每一个像素的颜色都是由红(Red)、绿(Green)、蓝(Blue)3个颜色通道合成的,即光的三原色。能表示2的n次方种颜色。

颜色的表示方式:如十进制:rgb(64, 224, 208)。十六进制:#40E0D0。

一张图片的理论大小:辨率是60x50,位深度是24。其理论大小为(60**50)*(24/8)=9000B≈8.79KB

YUV图片(原始图片数据,相当于pcm)

YUV比RGB的优势:(1)YUV比RGB的体积小一半。(2)YUV模式可以从黑白电视转为彩色电视过度

  • Y:表示亮度(Luminance、Luma),占8bit(1字节)。
  • Cb、Cr:表示色度(Chrominance、Chroma)
    • Cb(U):蓝色色度分量,占8bit(1字节)
    • Cr(V):红色色度分量,占8bit(1字节)

Y分量对清晰度影响巨大,所以可以减少UV分量以达到压缩的目的。(眼对亮度的敏感程度要高于对色度的敏感程度,人眼对于亮度的分辨要比对颜色的分辨精细一些)。即色度二次采样:如果在色度分量上进行(相对亮度分量)较低分辨率的采样,也就是存储较多的亮度细节、较少的色度细节,这样就可以在不明显降低画面质量的同时减小图像的体积。

注:YUV和RGB是可以进行转换的,如:

公式一:

c 复制代码
Y = 0.257R + 0.504G + 0.098B + 16
U = -0.148R - 0.291G + 0.439B + 128
V = 0.439R - 0.368G - 0.071B + 128
 
R = 1.164(Y - 16) + 2.018(U - 128)
G = 1.164(Y - 16) - 0.813(V - 128) - 0.391(U - 128)
B = 1.164(Y - 16) + 1.596(V - 128)

RGB的取值范围是[0,255]
Y的取值范围是[16,235]
UV的取值范围是[16,239]    

公式二:

c 复制代码
Y = 0.299R + 0.587G + 0.114B
U = 0.564(B - Y) = -0.169R - 0.331G + 0.500B
V = 0.713(R - Y) = 0.500R - 0.419G - 0.081B
 
R = Y + 1.403V
G = Y - 0.344U - 0.714V
B = Y + 1.770U
    
RGB的取值范围是[0, 1]
Y的取值范围是[0, 1]
UV的取值范围是[-0.5, 0.5]    

公式三:

c 复制代码
Y = 0.299R + 0.587G + 0.114B
U = -0.169R - 0.331G + 0.500B + 128
V = 0.500R - 0.419G - 0.081B + 128
 
R = Y + 1.403(V - 128)
G = Y - 0.343(U - 128) - 0.714(V - 128)
B = Y + 1.770(U - 128)

RGB的取值范围是[0, 255]
YUV的取值范围是[0, 255]

下面需要重点讲解一下色度二次采样通过何种方式达到高清晰小体积的

采样格式(像素格式)

若要进行色度二次采样,则采样格式有三种,A:B:C,A表示一块A*2个像素的概念区域,一般都是4。B表示第1行的色度采样数目。C表示第2行的色度采样数目(C的值一般要么等于B,要么等于0)。

以下假设YUV每个分量需要1字节

  • 4:4:4 -> yuv444p (8个像素,占24*8位,即24字节,每个像素为24/8=3字节)
  • 4:2:2 -> yuv422p (8个像素,占(24+8)*4位,即16字节,每个像素为16/8=2字节)
  • 4:2:0 -> yuv420p (8个像素,占(16+4*8) *2位,即12字节,每个像素为12/8=1.5字节)

如下是4*2像素的情况下,采样的方式:

色度二次采样记录每个位置的亮度信息,再记录部分位置的色度信息。结合起来就达到了,尽可能高的分辨率和尽可能大的压缩

采样完成之后,还要考虑YUV三个分量的存储方式(每个Y、U、V分别占一个字节,完整的YUV理论上是3字节,24位)

YUV数据的存储格式(YUV图片相当于pcm图片)

  • Planar(平面),Y、U、V分量分开单独存储,以p结尾
  • Semi-Planar(半平面),Y分量单独存储,U、V分量交错存储,以字母sp结尾
  • Packed(紧凑)又叫Interleaved (交错),Y、U、V分量交错存储

以yuv42p举例,将样式图按照字节流排列如下(下图应该代表24个像素,因为有24个Y分量)

采样格式+存储格式=像素格式

命令行其他格式图片转yuv图片

c 复制代码
ffmpeg -i 1.png -s 1928x1048 -pixel_format yuv420p out.yuv

yuv转其他图片

c 复制代码
ffmpeg -s 1928x1048 -pixel_format yuv420p -i in.yuv out.jpg

命令行播放yuv图片

c 复制代码
ffplay -video_size 1928x1048 -pixel_format yuv420p out.yuv

查看ffmpeg支持的像素格式

c 复制代码
ffmpeg -pix_fmts | findstr 444

四个分量则是yuva四个分量,a表示透明度

命令行yuv图片转其他格式

c 复制代码
ffmpeg -s 564x513 -pix_fmt yuv420p -i out.yuv 1.jpg

命令行查看摄像头的相关信息

c 复制代码
ffmpeg -h demuxer=dshow		---查看dshow支持的参数
    -video_size:分辨率
    -pixel_format:像素格式
    -framerate:帧率(每秒采集多少帧画面)
    -list_devices:true表示列出dshow支持的所有设备
    -list_options:true表示列出特定设备支持的所有参数
    
ffmpeg -f dshow -list_options true -i video="Integrated Camera"		---查看摄像头支持的参数(摄像头支持的像素格式和默认录制的格式可能不一样)

ffmpeg -f dshow -i video="Integrated Camera" out.yuv    	---使用摄像头录制视频

录制时的输出信息如下,即录制的视频信息为,分辨率:1280x720,像素格式:yuvj422p,帧率:30fps

c 复制代码
Input #0, dshow, from 'video=Integrated Camera':
    Stream #0:0: Video: mjpeg, yuvj422p, 1280x720, 30 fps
 
Output #0, rawvideo, to 'out.yuv':
    Stream #0:0: Video: rawvideo, yuvj422p, 1280x720, 30 fps

自定义参数进行摄像头录制

c 复制代码
#    
ffmpeg -f dshow -video_size 640x480 -pixel_format yuyv422 -framerate 30 -i video="Integrated Camera" out.yuv		---自定义参数录制视频    
    
#
ffplay -video_size 1280x720 -pixel_format yuvj422p -framerate 30 out.yuv 		---播放摄像头录制的视频  
    -framerate:帧率

音频录制及播放的最基本要素:

  • 分辨率:如1080x720,-video_size
  • 像素格式:即采样格式,如yuvj422p,-pixel_format
  • 帧率:-framerate

注:YUV中没有声音,只有在最基本的图像信息

完整代码:

RecordYuvThread.h

c 复制代码
#ifndef RECORDYUVTHREAD_H
#define RECORDYUVTHREAD_H

#include <QObject>
#include <QThread>

class RecordYuvThread : public QThread
{
    Q_OBJECT
public:
    explicit RecordYuvThread(QObject *parent = nullptr);
    ~RecordYuvThread();

signals:


    // QThread interface
protected:
    virtual void run() override;
};

#endif // RECORDYUVTHREAD_H

RecordYuvThread.cpp

c 复制代码
#include "recordyuvthread.h"

#include <QDebug>
#include <QFile>

extern "C" {
// 设备
#include <libavdevice/avdevice.h>
// 格式
#include <libavformat/avformat.h>
// 工具(比如错误处理)
#include <libavutil/avutil.h>
#include <libavutil/imgutils.h>
#include <libavcodec/avcodec.h>
}

#ifdef Q_OS_WIN
    // 格式名称
    #define FMT_NAME "dshow"
    // 设备名称
    #define DEVICE_NAME "video=Integrated Camera"
    // YUV文件名
    #define FILENAME "E:/media/out-yuyv422.yuv"
#else
    #define FMT_NAME "avfoundation"
    #define DEVICE_NAME "0"
    #define FILEPATH "/Users/mj/Desktop/out.yuv"
#endif

#define ERROR_BUF(ret) \
    char errbuf[1024]; \
    av_strerror(ret, errbuf, sizeof (errbuf));

RecordYuvThread::RecordYuvThread(QObject *parent) : QThread(parent)
{
    // 当监听到线程结束时(finished),就调用deleteLater回收内存
    connect(this,&RecordYuvThread::finished,this,[=](){
        this->deleteLater();
        qDebug()<<"RecordYuvThread线程结束";
    });
}

RecordYuvThread::~RecordYuvThread()
{
    // 断开所有的连接
    disconnect();
    // 内存回收之前,正常结束线程
    requestInterruption();
    // 安全退出
    quit();
    wait();
    qDebug() << this << "析构(内存被回收)";
}

void RecordYuvThread::run()
{
    // 获取输入格式对象
    AVInputFormat *fmt = av_find_input_format(FMT_NAME);
    if (!fmt) {
        qDebug() << "av_find_input_format error" << FMT_NAME;
        return;
    }

    // 格式上下文(将来可以利用上下文操作设备)
    AVFormatContext *ctx = nullptr;

    // 设备参数
    AVDictionary *options = nullptr;
    av_dict_set(&options, "video_size", "640x480", 0);
    av_dict_set(&options, "pixel_format", "yuyv422", 0);
    av_dict_set(&options, "framerate", "30", 0);

//    av_dict_set(&options, "video_size", "1280x720", 0);
//    av_dict_set(&options, "pixel_format", "yuyv422", 0);
//    av_dict_set(&options, "framerate", "10", 0);

    // 打开设备
    int ret = avformat_open_input(&ctx, DEVICE_NAME, fmt, &options);
    if (ret < 0) {
        ERROR_BUF(ret);
        qDebug() << "avformat_open_input error" << errbuf;
        return;
    }

    // 文件名
    QFile file(FILENAME);

    // 打开文件
    if (!file.open(QFile::WriteOnly)) {
        qDebug() << "file open error" << FILENAME;

        // 关闭设备
        avformat_close_input(&ctx);
        return;
    }

    // 计算一帧的大小
    AVCodecParameters *params = ctx->streams[0]->codecpar;
    AVPixelFormat pixFmt = (AVPixelFormat) params->format;

    int imageSize = av_image_get_buffer_size(
                        pixFmt,
                        params->width,
                        params->height,
                        1);

//    qDebug() << imageSize;
//    qDebug() << pixFmt << params->width << params->height;
//    qDebug() << av_pix_fmt_desc_get(pixFmt)->name;
//    int pixSize = av_get_bits_per_pixel(av_pix_fmt_desc_get(pixFmt)) >> 3;
//    int imageSize = params->width * params->height * pixSize;

    // 数据包
    AVPacket *pkt = av_packet_alloc();
    while (!isInterruptionRequested()) {
        // 不断采集数据
        ret = av_read_frame(ctx, pkt);

        if (ret == 0) { // 读取成功
            // 将数据写入文件
            file.write((const char *) pkt->data, imageSize);

            // windows:614400
            // mac:615680
            // qDebug() << pkt->size;

            // 释放资源
            av_packet_unref(pkt);
        } else if (ret == AVERROR(EAGAIN)) { // 资源临时不可用
            continue;
        } else { // 其他错误
            ERROR_BUF(ret);
            qDebug() << "av_read_frame error" << errbuf << ret;
            break;
        }
    }

    // 释放资源
    av_packet_free(&pkt);

    // 关闭文件
    file.close();

    // 关闭设备
    avformat_close_input(&ctx);
}

线程调用:

c 复制代码
void MainWindow::on_pushButton_record_yuv_clicked()
{
    if (!m_bIsRecordYuv) { // 点击了"开始录制"
        // 开启线程
        m_pRecordYuvThread = new RecordYuvThread(this);
        m_pRecordYuvThread->start();

        connect(m_pRecordYuvThread, &RecordYuvThread::finished,[this]() { // 线程结束
            m_pRecordYuvThread = nullptr;
            ui->pushButton_record_yuv->setText("开始录制");
        });

        // 设置按钮文字
        ui->pushButton_record_yuv->setText("结束录制");
        m_bIsRecordYuv=true;
    } 
    else { // 点击了"结束录制"
        // 结束线程
        m_pRecordYuvThread->requestInterruption();
        m_pRecordYuvThread = nullptr;

        // 设置按钮文字
        ui->pushButton_record_yuv->setText("开始录制");
        m_bIsRecordYuv=false;
    }
}

注意:.h文件中提前声明了以下全局变量

c 复制代码
	RecordYuvThread *m_pRecordYuvThread=nullptr;
    bool m_bIsRecordYuv=false;

注意:本文为个人记录,新手照搬可能会出现各种问题,请谨慎使用

码字不易,如果这篇博客对你有帮助,麻烦点赞收藏,非常感谢!有不对的地方

相关推荐
辰辰观影36 分钟前
Windows网盘视频播放器推荐:无广告4K HDR怎么选
音视频
2601_966827452 小时前
电视端云盘在线播放怎么选?支持4K HDR的应用推荐
音视频
开开心心就好3 小时前
音频降噪剪辑工具,音量裁剪一键处理
运维·服务器·前端·spring·ocr·音视频·1024程序员节
zslefour7 小时前
使用ffmpeg遍历获取音频文件相关信息
ffmpeg
AI视频工具指南7 小时前
AI视频的声音和画面怎么配合?先分清谁在传递信息
人工智能·aigc·音视频
辰辰观影9 小时前
无广告播放器推荐来啦
音视频
TrueDei9 小时前
GStreamer 使用 valve 分别控制音视频流的暂停与恢复
音视频
Android系统攻城狮10 小时前
Linux FFmpeg深度解析之avcodec_parameters_from_context调用流程与实战(十六)
linux·运维·ffmpeg·音视频进阶·ffmpeg音视频·车载音视频进阶
一条破秋裤10 小时前
基于 Hi3516CV610 的立定跳远视频测量系统调研与实施方案
arm开发·音视频
Android系统攻城狮11 小时前
Linux FFmpeg深度解析之avcodec_alloc_context3调用流程与实战(十四)
linux·运维·ffmpeg·车载音视频·音视频进阶·ffmpeg音视频进阶