ffmpeg入门小结(一)—— YUV格式示例

1.ffmpeg简介

ffmpeg是一个开源免费跨平台的视频和音频流方案,属于自由软件,采用LGPL/GPL许可证。它提供了录制、转换以及流化音视频的完整解决方案。它包含了非常先进的音频/视频编解码库libavcodec,为了保证高可移植性和编解码质量,libavcodec里很多codec都是从头开发的。ffmpeg是一套可以用来记录、转换数字音频、视频,并能将其转化为流的开源计算机程序。它包括了目前领先的音/视频编码库libavcodec。 ffmpeg是在Linux下开发出来的,但它可以在包括Windows在内的大多数操作系统中编译。

 

----------------------

ffmpeg的一些基本概念

----------------------

Container:在音视频中的“容器”,一般指的是一种特定格式的多媒体文件,里面指明了所包含的音视频、字幕等相关信息;
Stream:可以理解为分解开的、单纯的音频数据流或视频数据流或字幕流等;
Frame:指的是Stream中的一个未经压缩的完整数据单元;
Packet:是存储压缩数据的结构,视频流中通常是压缩的一个Frame的,而在音频流中则可能是压缩的多个Frame;
Codec:编解码器;

采样率:每秒钟记录多少个采样点;

 

----------------------

ffmpeg的组成结构

----------------------

ffmpeg主要由以下几个库组成:

· libavcodec: encoding/decoding library (编解码库)

· libavfilter: graph-based frame editing library(滤波器库,类似DShow的“可组装”的特点,但要求filter的输入/输出接口是匹配的)

· libavformat: I/O and muxing/demuxing library(IO及分解/合并流)

· libavdevice: special devices muxing/demuxing library(例如打开摄像头,屏幕录制等)

· libavutil: common utility library(小型函数库,内存分配、释放)

· libswresample: audio resampling, format conversion and mixing

· libpostproc: post processing library(后期处理?尚不清楚)

· libswscale: color conversion and scaling library(转码及尺度变换)

ffmpeg软件包经编译过后将生成三个可执行文件(可通过命令行运行):

· ffmpeg:ffmpeg用于对媒体文件进行处理;

· ffserver:ffserver是一个http的流媒体服务器;

· ffplay:ffplay是一个基于SDL的简单播放器。

2.视频YUV格式简介

 

YUV格式,一般用Y,U,V三者的比率来表示不同格式,比如YUV444 表示三者是比值此是 4:4:4,即一个点数据点,Y,U,V的空间都是一样大小。目前主要有如下比例,注意所有格式中Y比值都是4,占一个字节,表示没有减少采样。不同格式中,减小只是UV的采样值.

 

4:4:4:表示色度值(UV)没有减少采样。即Y,U,V各占一个字节,总共占3字节。 

4:2:2:表示UV分量采样减半,比如第一个像素采样Y,U,第二个像素采样Y,V,依次类推,这样每个点占用2个字节。二个像素组成一个宏像素.

4:2:0:这种采样并不意味着只有Y,Cb而没有Cr分量,这里的0说的U,V分量隔行才采样一次。比如第一行采样 4:2:0,第二行采样 4:0:2 ,依次类推……在这种采样方式下,平均每一个像素占用1.5字节.

 

4:2:2示例

如果原始数据三个像素是 [Y0 U0 V0] ,[Y1 U1 V1],[Y2 U2 V2],[Y3 U3 V3]

经过4:2:2采样后,数据变成了 Y0 U0 ,Y1 V1 ,Y2 U2,Y3 V3

如果还原后,因为某一些数据丢失就补成 [Y0 U0 V1],[Y1 U0 V1],[Y2 U2 V3] ,[Y3 U3 V2]

 

4:2:0示例

下面八个像素为:[Y0 U0 V0] [Y1 U1 V1] [Y2 U2 V2] [Y3 U3 V3] [Y5 U5 V5] [Y6 U6 V6] [Y7U7 V7] [Y8 U8 V8]

存放的码流为:  Y0 U0, Y1, Y2 U2, Y3,Y5 V5, Y6,Y7 V7, Y8

映射出的像素点为:[Y0 U0 V5] [Y1 U0 V5] [Y2 U2 V7] [Y3 U2 V7] [Y5 U0 V5] [Y6 U0 V5] [Y7 U2 V7] [Y8 U2 V7]

 

   除了4:4:4采样,其余采样后信号重新还原显示后,会丢失部分UV数据,只能用相临的数据补齐,但人眼对UV不敏感,因此总体感觉损失不大。

 

                                                                                    图1 YUV420sp存储格式

                                                                                   图2 YUV420p存储格式

ps: ffmpeg中,图像原始数据包括两种:planar和packed。planar就是将几个分量分开存,比如YUV420中,data[0]专门存Y,data[1]专门存U,data[2]专门存V。而packed则是打包存,所有数据都存在data[0]中.

3.YUV格式示例【顺时针旋转90°YUV420图像】:

public static void rotateYUV420(byte[] src,byte[] des,int width,int height, String type){
        // 原图按行序存储
		int wh = width * height;
		
		int k = 0;
		//旋转Y分量(按原图的列序存储)
		for(int i=0;i<width;i++) {
			for(int j=0;j<height;j++) {
	              des[k] = src[width*j + i];			
			      k++;
			}
		}

if(type.equals(“YUV420SP”)){
        // YUV420SP是UV分量交错依次存储的情况
		// 旋转UV分量(按原图的列序存储)
		for(int i=0;i<width;i+=2) {  // +2
			for(int j=0;j<height/2;j++) {	
	              des[k] = src[wh+ width*j + i];	
	              des[k+1]=src[wh + width*j + i+1];
			      k+=2;
			}
		}

}
else if(type.equals(“YUV420P”)){
        // YUV420P是UV分量分开存储的情况
        // 旋转U分量(按原图的列序存储)
		for(int i=0;i<width;i++) {
			for(int j=0;j<height/4;j++) {	
                  // stride = wh
	              des[k] = src[wh + width*j + i];	
			      k++;
			}
		}

        // 旋转V分量(按原图的列序存储)
		for(int i=0;i<width;i++) {
			for(int j=0;j<height/4;j++) {	
                  // stride = wh + wh*1/4
	              des[k] = src[(1+1/4.0f)*wh + width*j + i];	
			      k++;
		 	}
		  }
}
	}

 

ps: ffmpeg库的接口都是c函数,在cpp文件里调用ffmpeg函数要注意了,由于C++语言支持函数重载,C语言不支持函数重载,函数被C++编译器编译后在库中的名字与C语言的不同(mangled name),一个用C写成的库如果想被C/C++同时可以使用,那在头文件应该加上以下代码

#ifdef __cplusplus
extern "C" {
#endif 

...
#ifdef __cplusplus
}
#endif

 

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
MediaCodec 是 Android 平台上的一个多媒体编解码器,用于对视频和音频进行编解码处理。由于 MediaCodec 是 Android 平台独有的 API,因此 C++ 直接调用 MediaCodec 进行视频解码可能不太方便。不过,如果你仍然想要使用 C++ 编写 MP4 文件解码为 YUV 格式的程序,可以考虑以下步骤: 1. 使用 FFmpeg 或者其他的开源库对 MP4 文件进行解码。这些开源库提供了一些 API,可以方便地将 MP4 文件解码为 YUV 格式视频数据。例如,可以使用 FFmpeg 的 avcodec_decode_video2() 函数将 MP4 文件解码为 YUV 格式视频数据。 2. 在解码过程中,需要将解码出来的视频数据保存到内存中。可以使用 C++ 的动态内存分配方式,例如 new 或者 malloc 函数来分配存储视频数据的内存。需要注意的是,内存的大小应该根据视频的分辨率、帧率和像素格式来确定。 3. 将解码出来的 YUV 格式视频数据保存到文件中。可以使用 C++ 的文件操作函数,例如 fopen、fwrite 等函数将视频数据写入文件中。 代码示例: ```c++ #include <iostream> #include <fstream> #include <string.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <fcntl.h> #include <errno.h> #include <sys/stat.h> #include <sys/types.h> #include <libavcodec/avcodec.h> #include <libavformat/avformat.h> #include <libswscale/swscale.h> using namespace std; // 输入文件名 const char* input_filename = "input.mp4"; // 输出文件名 const char* output_filename = "output.yuv"; int main(int argc, char* argv[]) { int ret; AVFormatContext* fmt_ctx = NULL; AVCodecContext* codec_ctx = NULL; AVCodec* codec = NULL; AVPacket pkt; AVFrame* frame = NULL; int video_stream_idx = -1; FILE* fp_out = NULL; int frame_count = 0; // 1. 打开输入文件 ret = avformat_open_input(&fmt_ctx, input_filename, NULL, NULL); if (ret < 0) { av_log(NULL, AV_LOG_ERROR, "Could not open input file '%s'", input_filename); return ret; } // 2. 查找视频流 ret = avformat_find_stream_info(fmt_ctx, NULL); if (ret < 0) { av_log(NULL, AV_LOG_ERROR, "Could not find stream information"); goto end; } for (int i = 0; i < fmt_ctx->nb_streams; i++) { AVStream* stream = fmt_ctx->streams[i]; if (stream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { video_stream_idx = i; break; } } if (video_stream_idx == -1) { av_log(NULL, AV_LOG_ERROR, "Could not find video stream"); ret = -1; goto end; } // 3. 打开视频解码器 codec_ctx = avcodec_alloc_context3(NULL); if (!codec_ctx) { av_log(NULL, AV_LOG_ERROR, "Could not allocate video codec context"); ret = AVERROR(ENOMEM); goto end; } ret = avcodec_parameters_to_context(codec_ctx, fmt_ctx->streams[video_stream_idx]->codecpar); if (ret < 0) { av_log(NULL, AV_LOG_ERROR, "Could not copy codec parameters to codec context"); goto end; } codec = avcodec_find_decoder(codec_ctx->codec_id); if (!codec) { av_log(NULL, AV_LOG_ERROR, "Could not find decoder for codec ID %d", codec_ctx->codec_id); goto end; } ret = avcodec_open2(codec_ctx, codec, NULL); if (ret < 0) { av_log(NULL, AV_LOG_ERROR, "Could not open codec"); goto end; } // 4. 分配解码帧内存 frame = av_frame_alloc(); if (!frame) { av_log(NULL, AV_LOG_ERROR, "Could not allocate frame"); ret = AVERROR(ENOMEM); goto end; } // 5. 打开输出文件 fp_out = fopen(output_filename, "wb+"); if (!fp_out) { av_log(NULL, AV_LOG_ERROR, "Could not open output file '%s'", output_filename); ret = -1; goto end; } // 6. 解码视频帧 while (1) { ret = av_read_frame(fmt_ctx, &pkt); if (ret < 0) { break; } if (pkt.stream_index == video_stream_idx) { ret = avcodec_send_packet(codec_ctx, &pkt); if (ret < 0) { av_log(NULL, AV_LOG_ERROR, "Error sending a packet for decoding"); break; } while (1) { ret = avcodec_receive_frame(codec_ctx, frame); if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) { break; } else if (ret < 0) { av_log(NULL, AV_LOG_ERROR, "Error during decoding"); goto end; } if (frame_count++ % 25 == 0) { // 7. 将解码出来的 YUV 数据保存到文件中 for (int i = 0; i < codec_ctx->height; i++) { fwrite(frame->data[0] + i * frame->linesize[0], 1, codec_ctx->width, fp_out); } for (int i = 0; i < codec_ctx->height / 2; i++) { fwrite(frame->data[1] + i * frame->linesize[1], 1, codec_ctx->width / 2, fp_out); } for (int i = 0; i < codec_ctx->height / 2; i++) { fwrite(frame->data[2] + i * frame->linesize[2], 1, codec_ctx->width / 2, fp_out); } } } } av_packet_unref(&pkt); } end: avformat_close_input(&fmt_ctx); avcodec_free_context(&codec_ctx); av_frame_free(&frame); if (fp_out) { fclose(fp_out); } return ret; } ``` 这段代码使用 FFmpeg 库将 MP4 文件解码为 YUV 格式视频数据,并将视频数据保存到文件中。需要注意的是,代码中只是将每隔 25 帧的视频数据保存到文件中,你可以根据需要调整保存视频数据的频率。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ShaderJoy

您的打赏是我继续写博客的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值