Linux音视频开发之二:转换YUY2到I420便于压缩成h264

Stella981
• 阅读 1002

在用libx264做h264压缩的时候,我们可以通过命令 ffmpeg -h encoder=libx264 来查看它所支持的输入格式

Encoder libx264 [libx264 H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10]:
    General capabilities: delay threads 
    Threading capabilities: auto
    Supported pixel formats: yuv420p yuvj420p yuv422p yuvj422p yuv444p yuvj444p nv12 nv16 nv21

yuv422p不是YUY2, 可以看见我从USB摄像头直接取出的数据YUY2是不能直接当作输入格式的。(采集YUY2数据查看上一篇文章https://my.oschina.net/noevilme/blog/4462358

常有3种方案来做转换:

  1. 自己按像素点做转换
  2. 使用libyuv
  3. 使用ffmpeg swscale

我推荐第2种和第3种, 大佬们的算法都有优化的,除非你觉得自己更牛X。据江湖传言libyuv是swscale的几倍速度,我没测过哈。

转换前先了解一下YUY2和I420数据格式,话说YUV各种名称实在有点乱,参考http://fourcc.org/yuv.php

YUY2, YUYV, YUV422  这三个都是YUY2的别称,ffmpeg定义AV_PIX_FMT_YUYV422。

    Y U Y V Y U Y V
    Y U Y V Y U Y V
    Y U Y V Y U Y V
    Y U Y V Y U Y V
    Y U Y V Y U Y V
    Y U Y V Y U Y V
    Y U Y V Y U Y V  

I420, IYUV, YUV420P, YU12, 前面这几个都是I420的名字,其中YUV420P又是几个格式的统称,特定环境下就是I420,ffmpeg定义AV_PIX_FMT_YUV420P。

    Y Y Y Y Y Y Y Y
    Y Y Y Y Y Y Y Y
    Y Y Y Y Y Y Y Y
    Y Y Y Y Y Y Y Y
    U U U U U U U U
    V V V V V V V V

一、使用libyuv::YUY2ToI420

libyuv需要自己编译安装https://github.com/lemenkov/libyuv

// Convert YUY2 to I420.
  LIBYUV_API
  int YUY2ToI420(const uint8_t* src_yuy2,
                 int src_stride_yuy2,
                 uint8_t* dst_y,
                 int dst_stride_y,
                 uint8_t* dst_u,
                 int dst_stride_u,
                 uint8_t* dst_v,
                 int dst_stride_v,                                                                                                                                                                          
                 int width,
                 int height);

上面是接口定义,stride参数是指图片格式行距。

int libyuv_convert(const char *input_file, const char *output_file, int width,
                   int height) {
    FILE *in_fp = fopen(input_file, "rb");
    if (!in_fp) {
        std::cout << "open input failure" << std::endl;
        return 1;
    }
    FILE *out_fp = fopen(output_file, "wb");
    if (!out_fp) {
        std::cout << "open output failure" << std::endl;
        return 1;
    }

    uint8_t *yuy2_image = new uint8_t[width * height * 2];
    uint8_t *i420_image = new uint8_t[width * height * 3 / 2];

    while (fread(yuy2_image, 1, width * height * 2, in_fp) ==
           (size_t)width * height * 2) {
        uint8_t *i420_y = i420_image;
        uint8_t *i420_u = i420_y + width * height;
        uint8_t *i420_v = i420_u + width * height / 4;
        libyuv::YUY2ToI420(yuy2_image, width * 2, i420_y, width, i420_u,
                           width / 2, i420_v, width / 2, width, height);

        fwrite(i420_image, 1, width * height * 3 / 2, out_fp);
    }

    delete[] i420_image;
    delete[] yuy2_image;
    fclose(in_fp);
    fclose(out_fp);

    return 0;
}

二、使用sws_scale

swscale库是ffmpeg的一部分,所以在操作的时候用AVFrame结构更加方便,不然得自己定义一个二维数组。

int ffmpeg_convert2(const char *input_file, const char *output_file, int width,
                    int height) {
    SwsContext *context;
    FILE *in_fp, *out_fp;

    in_fp = fopen(input_file, "rb");
    if (!in_fp) {
        std::cout << "open input failure" << std::endl;
        return 1;
    }

    out_fp = fopen(output_file, "wb");
    if (!out_fp) {
        std::cout << "open out file failure" << std::endl;
        fclose(in_fp);
        return 1;
    }

    context = sws_getContext(width, height, AV_PIX_FMT_YUYV422, width, height,
                             AV_PIX_FMT_YUV420P, SWS_FAST_BILINEAR, nullptr,
                             nullptr, nullptr);

    AVFrame *av_frame_in = av_frame_alloc();
    auto yuy2_image_size =
        av_image_alloc(av_frame_in->data, av_frame_in->linesize, width, height,
                       AV_PIX_FMT_YUYV422, 1);

    AVFrame *av_frame_out = av_frame_alloc();
    auto i420_image_size =
        av_image_alloc(av_frame_out->data, av_frame_out->linesize, width,
                       height, AV_PIX_FMT_YUV420P, 1);

    while (fread(av_frame_in->data[0], 1, yuy2_image_size, in_fp) ==
           (size_t)yuy2_image_size) {

        sws_scale(context, av_frame_in->data, av_frame_in->linesize, 0, height,
                  av_frame_out->data, av_frame_out->linesize);

        fwrite(av_frame_out->data[0], 1, i420_image_size, out_fp);
    }

    sws_freeContext(context);

    av_freep(&av_frame_in->data[0]);
    av_freep(&av_frame_out->data[0]);
    av_frame_free(&av_frame_in);
    av_frame_free(&av_frame_out);
    fclose(in_fp);
    fclose(out_fp);

    return 0;
}

调用函数

#include <cstdio>
#include <iostream>
#include <libyuv/convert_from.h>
#include <libyuv/cpu_id.h>
#include <libyuv/planar_functions.h>
#include <libyuv/row.h>

extern "C" {
#include <libavformat/avformat.h>
#include <libavutil/imgutils.h>
#include <libswscale/swscale.h>
#include <libyuv.h>
#include <libyuv/convert.h>
};


int main(int argc, char **argv) {
    const char *input_file =
        "1280x720_yuy2.yuv";
    const char *output_file = "yuv_1280x720_i420.yuv";

    libyuv_convert(input_file, output_file, 1280, 720);
    ffmpeg_convert2(input_file, "ff_1280x720_i420.yuv", 1280, 720);

    return 0;
}
点赞
收藏
评论区
推荐文章
blmius blmius
2年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Wesley13 Wesley13
2年前
java将前端的json数组字符串转换为列表
记录下在前端通过ajax提交了一个json数组的字符串,在后端如何转换为列表。前端数据转化与请求varcontracts{id:'1',name:'yanggb合同1'},{id:'2',name:'yanggb合同2'},{id:'3',name:'yang
Jacquelyn38 Jacquelyn38
2年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Easter79 Easter79
2年前
Twitter的分布式自增ID算法snowflake (Java版)
概述分布式系统中,有一些需要使用全局唯一ID的场景,这种时候为了防止ID冲突可以使用36位的UUID,但是UUID有一些缺点,首先他相对比较长,另外UUID一般是无序的。有些时候我们希望能使用一种简单一些的ID,并且希望ID能够按照时间有序生成。而twitter的snowflake解决了这种需求,最初Twitter把存储系统从MySQL迁移
Wesley13 Wesley13
2年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
2年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Stella981 Stella981
2年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Wesley13 Wesley13
2年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
3个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这