使用c++处理YUV文件

YUV文件的存储结构

取样结构

这组图图每一行即为一行像素点,以Y为基准,蓝色和红色表示 C b / U C_b/U Cb/U C r / V C_r/V Cr/V

4:4:4

4:4:4

4:2:2

4:2:2

4:1:1

4:1:1

4:2:0格式1

4:2:0-1

4:2:0格式2

4:2:0-2

存储结构

一下均指一帧的情况,实际上是一帧内容按照相应格式存储完成后,再存储下一帧。

YUVY(4:2:2)

Y 0 C b 0 Y 1 C r 0 Y 2 C b 1 Y 3 C r 2 ⋯ Y_0C_{b_0}Y_1C_{r_0}Y_2C_{b_1}Y_3C_{r_2}\cdots Y0Cb0Y1Cr0Y2Cb1Y3Cr2
Y Y Y后跟 C b C_b Cb Y Y Y后跟 C r C_r Cr交替出现。

UYVY(4:2:2)

C b 0 Y 0 C r 0 Y 1 C b 1 Y 2 C r 2 Y 3 ⋯ C_{b_0}Y_0C_{r_0}Y_1C_{b_1}Y_2C_{r_2}Y_3\cdots Cb0Y0Cr0Y1Cb1Y2Cr2Y3
C b C_b Cb后跟 Y Y Y C r C_r Cr后跟 Y Y Y交替出现。

YUV422P(4:2:2)

先存储完所有Y,再存储完所有 C b C_b Cb,最后再存储所有 C r C_r Cr

YV12,YU12(4:2:0)

先存储完所有Y,再存储完所有 C b C_b Cb,最后再存储所有 C r C_r Cr

NV12,NV21(4:2:0)

先存储完所有Y,之后 C b x C_{b_x} Cbx C b x + 1 C_{b_{x+1}} Cbx+1 C r x C_{r_x} Crx C r x + 1 C_{r_{x+1}} Crx+1的结构交替出现,即两字节 C b C_b Cb之后再存储两字节 C r C_r Cr

所占空间计算

每一个像素点用1字节存储信息。知道分辨率,即高 h h h和宽度 w w w之后, h × w h\times w h×w即是Y所占空间大小,再根据取样结构算出U和V所占比例,相加即为一帧所占大小。
如4:2:0格式,Y占用 h × w h\times w h×w,U和V占用 h × w × 2 4 h\times{w}\times\frac{2}{4} h×w×42,加起来一帧所占的空间就是 3 h w 2 \frac{3hw}{2} 23hw

使用c++进行处理

知道文件怎么存储之后,就可以进行处理了。以288乘352的YV12格式为例。
如果我想把它变成黑白的,那么我只需要保留Y的信息,其余都抹掉即可。那么去掉U,V是否是把他们变成0呢?并不是,因为U,V有负值,而处理数据的时候却不按照负值处理,于是U,V的0的位置,被128代替;也就是说128才是实际意义上的0。

#include <fstream>
#include <iostream>
using namespace std;
const char* inPath = "miss.cif";
const char* outPath = "miss_out.cif";
int main()
{
	ifstream in(inPath, ios::binary);
	in.seekg(0, ios::end);
	int size = in.tellg();
	in.seekg(0, ios::beg);

	char* inBuffer = new char[size];
	in.read(inBuffer, size);
	in.close();

	char* outBuffer = new char[size];
	int width = 352, height = 288, inFrameSize = width * height * 3 / 2;
	int frameNum = size / inFrameSize;

	for (int i = 0; i < frameNum; ++i)
		for (int j = 0; j < inFrameSize; ++ j)
			outBuffer[i * inFrameSize + j] = j < width * height ? inBuffer[i * inFrameSize + j] : 128;

	ofstream out(outPath,ios::binary);
	out.write(outBuffer, size);
	out.close();
}

处理完成后使用YUV播放器查看效果:
处理前:
before0before1
处理后:
after0
after1

  • 4
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 10
    评论
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
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值