音视频-像素格式转换

音视频-SDL播放YUV(下)成功播放yuv裸流数据之后, 下载的是对应格式的yuv420p的数据.

如果自己通过Mac, 或者是其他摄像头录制的视频, 支持的格式不一定是yuv420p, 我自己通过 ffmpeg -f avfoundation -framerate 30 -i 0 out.yuv在Mac上录制的yuv视频格式默认就是uyvy422的像素格式, 所以通过音视频-SDL播放YUV(下)中直接播放, 会因为像素格式不对, 导致播放异常.

Mac主要看输出信息 Output #0
-pixel_format : 像素格式 uyvy422
-framerate : 帧率 30
-video_size : 视频大小1280x720

所以这个时候需要把uyvy422的像素格式转换为420p的像素格式.



需要用到的ffmpeg的库 #include <libswscale/swscale.h>

核心函数sws_scale

/**
 * Scale the image slice in srcSlice and put the resulting scaled
 * slice in the image in dst. A slice is a sequence of consecutive
 * rows in an image.
 *
 * Slices have to be provided in sequential order, either in
 * top-bottom or bottom-top order. If slices are provided in
 * non-sequential order the behavior of the function is undefined.
 *
 * @param c         the scaling context previously created with
 *                  sws_getContext()
 * @param srcSlice  the array containing the pointers to the planes of
 *                  the source slice
 * @param srcStride the array containing the strides for each plane of
 *                  the source image
 * @param srcSliceY the position in the source image of the slice to
 *                  process, that is the number (counted starting from
 *                  zero) in the image of the first row of the slice
 * @param srcSliceH the height of the source slice, that is the number
 *                  of rows in the slice
 * @param dst       the array containing the pointers to the planes of
 *                  the destination image
 * @param dstStride the array containing the strides for each plane of
 *                  the destination image
 * @return          the height of the output slice
 */
int sws_scale(struct SwsContext *c, const uint8_t *const srcSlice[],
              const int srcStride[], int srcSliceY, int srcSliceH,
              uint8_t *const dst[], const int dstStride[]);

思路跟播放yuv视频差不多

  • 把 uyvy422 转为 yuv420p
  • uyvy422 的数据格式 uyvy uyvy uyvy uyvy 占2个字节
  • yuv420p 的数据格式 yyyy yyyy uu vv 占1.5个字节
  • 整体思路, 一帧的uyvy422 转为 一帧的yuv420p
  • 简单粗暴 : 读取输入源文件, 每次读取一帧的数据, 读取传入buffer缓冲区, 通过核心函数sws_scale, 转换到输出缓冲区, 最后写入目标文件中

核心代码

#include "swsscalethread.h"
#include <QFile>
#include <QDebug>

extern "C" {
#include <libswscale/swscale.h>
#include <libavutil/imgutils.h>
#include <libswresample/swresample.h>

}


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


#define CHECK_END(ret, funcStr) \
    if (ret) { \
        SWS_ERROR_BUF(ret); \
        qDebug() << #funcStr << " error :" << errbuf; \
        goto end; \
    }


SwsScaleThread::SwsScaleThread(QObject *parent) : QThread(parent) {
    // 当监听到线程结束时(finished),就调用deleteLater回收内存
    connect(this, &SwsScaleThread::finished,
            this, &SwsScaleThread::deleteLater);
}

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


void SwsScaleThread::run() {
    // ffplay -video_size 1280X720 -pixel_format uyvy422 -framerate 30 /Users/liliguang/Desktop/record_to_yuv.yuv
    // 把 uyvy422 转为 yuv420p
    // uyvy422 的数据格式  uyvy uyvy uyvy uyvy   占2个字节
    // yuv420p 的数据格式  yyyy yyyy uu vv       占1.5个字节
    // 整体思路, 一帧的uyvy422 转为 一帧的yuv420p
    // 简单粗暴 : 读取输入源文件, 每次读取一帧的数据, 读取传入buffer缓冲区, 通过核心函数sws_scale, 转换到输出缓冲区, 最后写入目标文件中


    // 输入源
    int srcW = 1280;
    int srcH = 720;
    AVPixelFormat srcFormat = AV_PIX_FMT_UYVY422;
    int srcImageSize = srcW * srcH * 2;

    // 输入源buffer缓冲区
    // 这里的4根据av_image_alloc()中的参数来决定的. 
    uint8_t *srcData[4];
    int srclinesizes[4];

    // 创建srcData缓冲区Ret
    int srcDataRet;
    // 输入源文件
    const char *srcFilePath = "/Users/liliguang/Desktop/srcYuv.yuv";


    // 输出源
    int dstW = 640;
    int dstH = 360;
    AVPixelFormat dstFormat = AV_PIX_FMT_YUV420P;
    int dsrImageSize = dstW * dstH * 1.5;

    // 输出源buffer缓冲区
    uint8_t *dstData[4];
    int dstlinesizes[4];
    int dstDataRet;

    const char *dstFilePath = "/Users/liliguang/Desktop/dstYuv.yuv";

    QFile srcFile(srcFilePath);
    QFile dstFile(dstFilePath);

    int openSrcFileRet;
    int openDstFileRet;



    struct SwsContext *context;
    int swsScaleRet;

    // 第一步 : 文件操作
    openSrcFileRet = srcFile.open(QFile::ReadOnly);
    CHECK_END(!openSrcFileRet, "srcFile open");

    openDstFileRet = dstFile.open(QFile::WriteOnly);
    CHECK_END(!openDstFileRet, "dstFile open");



    // 第二步 : 创建上下文,  后面四个参数参照官方Demo
    context = sws_getContext(
                srcW, srcH, srcFormat,
                dstW, dstH, dstFormat,
                SWS_BILINEAR, NULL,NULL,NULL
                );
    CHECK_END(!context, "sws_getContext");



    // 第三步 : 创建buffer缓冲区
    srcDataRet =  av_image_alloc(srcData, srclinesizes, srcW, srcH, srcFormat, 16);
    CHECK_END(!srcDataRet, "srcData av_image_alloc");
    qDebug() <<"srcDataRet : " << srcDataRet ;

    dstDataRet =  av_image_alloc(dstData, dstlinesizes, dstW, dstH, dstFormat, 16);
    CHECK_END(!dstDataRet, "dstData av_image_alloc");
    qDebug() <<"dstDataRet : " << dstDataRet ;


    qDebug() <<"开始读取文件" ;

    int readRet;
    int writeRet;
    while( (readRet = srcFile.read((char *)srcData[0], srcImageSize)) > 0) {
        qDebug() << "readRet : " << readRet;
        // 转换
        swsScaleRet = sws_scale(context, srcData, srclinesizes, 0, srcH, dstData, dstlinesizes );
        qDebug() << "swsScaleRet : " << swsScaleRet;

        // 写入文件
        writeRet = dstFile.write((char *)dstData[0], dsrImageSize);
        CHECK_END(!writeRet, "dstFile write");

    }
    qDebug() << "readRet : " << readRet;
    qDebug() <<"结束读取文件" ;



end:
    // 关闭文件,释放资源
    srcFile.close();
    dstFile.close();

    // 释放输入缓冲区
    av_freep(&srcData);
    av_freep(&dstData);

    // 释放重采样上下文
    sws_freeContext(context);
}

转换结果

粗略计算
输入源的总大小 Src : 248832000
输入源的一帧的大小 imageSize : 1280 * 720 * 2 = 1843200
输入源的总共有多少帧 Frame : 248832000 / 1843200 = 135

输出源的总大小 Dst : 46656000
输出源的一帧的大小imageSize : 640 * 360 * 1.5 = 345600
输出源的总共有多少帧 Frame : 46656000 / 345600 = 135

通过ffmpeg命令行播放srcYuv.yuv

输入文件

也可以通过 Stream #0:0: Video: rawvideo (UYVY / 0x59565955), uyvy422, 1280x720, 442368 kb/s, 30 tbr, 30 tbn 看到相关的输出参数

通过ffmpeg命令行播放dstYuv.yuv

目标文件

最后, 通过自己的音视频-SDL播放YUV(下)播放

QT播放
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,463评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,868评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,213评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,666评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,759评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,725评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,716评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,484评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,928评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,233评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,393评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,073评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,718评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,308评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,538评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,338评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,260评论 2 352

推荐阅读更多精彩内容