ffmpeg 编码yuv数据保存文件
时间: 2023-07-25 11:39:59 浏览: 121
要将 YUV 数据编码为视频文件,可以使用 ffmpeg 库提供的 API。以下是一个简单的示例代码:
```c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <libavutil/imgutils.h>
#include <libavutil/opt.h>
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
int main(int argc, char *argv[]) {
int ret = 0;
AVFormatContext *format_ctx = NULL;
AVOutputFormat *output_fmt = NULL;
AVStream *video_stream = NULL;
AVCodec *codec = NULL;
AVCodecContext *codec_ctx = NULL;
AVPacket pkt = { 0 };
int video_frame_count = 0;
int video_width = 640;
int video_height = 480;
int video_fps = 25;
const char *output_filename = "output.mp4";
// 初始化 FFmpeg 库
av_register_all();
// 打开输出文件
ret = avformat_alloc_output_context2(&format_ctx, NULL, NULL, output_filename);
if (ret < 0) {
fprintf(stderr, "Failed to allocate output format context: %s\n", av_err2str(ret));
return ret;
}
output_fmt = format_ctx->oformat;
// 添加视频流
codec = avcodec_find_encoder(output_fmt->video_codec);
if (!codec) {
fprintf(stderr, "Failed to find video encoder\n");
ret = AVERROR(EINVAL);
goto end;
}
video_stream = avformat_new_stream(format_ctx, codec);
if (!video_stream) {
fprintf(stderr, "Failed to create video stream\n");
ret = AVERROR(EINVAL);
goto end;
}
codec_ctx = avcodec_alloc_context3(codec);
if (!codec_ctx) {
fprintf(stderr, "Failed to allocate codec context\n");
ret = AVERROR(ENOMEM);
goto end;
}
codec_ctx->width = video_width;
codec_ctx->height = video_height;
codec_ctx->pix_fmt = AV_PIX_FMT_YUV420P;
codec_ctx->time_base = (AVRational) { 1, video_fps };
codec_ctx->framerate = (AVRational) { video_fps, 1 };
codec_ctx->gop_size = 10;
codec_ctx->max_b_frames = 1;
codec_ctx->bit_rate = 400000;
codec_ctx->rc_min_rate = codec_ctx->rc_max_rate = codec_ctx->bit_rate;
codec_ctx->sample_aspect_ratio = (AVRational) { 1, 1 };
if (format_ctx->oformat->flags & AVFMT_GLOBALHEADER)
codec_ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
ret = avcodec_open2(codec_ctx, codec, NULL);
if (ret < 0) {
fprintf(stderr, "Failed to open video encoder: %s\n", av_err2str(ret));
goto end;
}
ret = avcodec_parameters_from_context(video_stream->codecpar, codec_ctx);
if (ret < 0) {
fprintf(stderr, "Failed to copy codec parameters: %s\n", av_err2str(ret));
goto end;
}
av_dump_format(format_ctx, 0, output_filename, 1);
// 打开输出文件并写文件头
ret = avio_open(&format_ctx->pb, output_filename, AVIO_FLAG_WRITE);
if (ret < 0) {
fprintf(stderr, "Failed to open output file: %s\n", av_err2str(ret));
goto end;
}
ret = avformat_write_header(format_ctx, NULL);
if (ret < 0) {
fprintf(stderr, "Failed to write file header: %s\n", av_err2str(ret));
goto end;
}
// 编码 YUV 数据并写入文件
int y_size = video_width * video_height;
int uv_size = y_size / 4;
uint8_t *y_data = (uint8_t *) malloc(y_size);
uint8_t *u_data = (uint8_t *) malloc(uv_size);
uint8_t *v_data = (uint8_t *) malloc(uv_size);
for (int i = 0; i < 300; i++) {
// 生成测试数据
for (int j = 0; j < y_size; j++) {
y_data[j] = i % 255;
}
for (int j = 0; j < uv_size; j++) {
u_data[j] = (i + j) % 255;
v_data[j] = (i + j + uv_size) % 255;
}
// 将 YUV 数据填充到 AVFrame 中
AVFrame *frame = av_frame_alloc();
frame->width = video_width;
frame->height = video_height;
frame->format = AV_PIX_FMT_YUV420P;
av_image_fill_arrays(frame->data, frame->linesize, y_data, video_width, u_data, video_width / 2, v_data, video_width / 2, AV_PIX_FMT_YUV420P, 1);
// 编码 AVFrame 并写入文件
ret = avcodec_send_frame(codec_ctx, frame);
if (ret < 0) {
fprintf(stderr, "Error sending frame: %s\n", av_err2str(ret));
av_frame_free(&frame);
goto end;
}
while (ret >= 0) {
ret = avcodec_receive_packet(codec_ctx, &pkt);
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
break;
} else if (ret < 0) {
fprintf(stderr, "Error receiving packet: %s\n", av_err2str(ret));
goto end;
}
pkt.stream_index = video_stream->index;
av_packet_rescale_ts(&pkt, codec_ctx->time_base, video_stream->time_base);
pkt.pts = video_frame_count * video_stream->time_base.den / (video_stream->time_base.num * video_fps);
pkt.dts = pkt.pts;
pkt.duration = video_stream->time_base.den / (video_stream->time_base.num * video_fps);
pkt.pos = -1;
av_interleaved_write_frame(format_ctx, &pkt);
av_packet_unref(&pkt);
}
av_frame_free(&frame);
video_frame_count++;
}
// 写文件尾并释放资源
av_write_trailer(format_ctx);
end:
if (codec_ctx) {
avcodec_free_context(&codec_ctx);
}
if (format_ctx) {
if (format_ctx->pb) {
avio_closep(&format_ctx->pb);
}
avformat_free_context(format_ctx);
}
if (ret < 0) {
fprintf(stderr, "Error occurred: %s\n", av_err2str(ret));
return 1;
}
return 0;
}
```
在这个示例代码中,我们首先使用 avformat_alloc_output_context2 函数创建一个 AVFormatContext 对象,然后使用 avformat_new_stream 函数为该对象添加一个视频流。接着,我们使用 AVCodecContext 结构体设置了视频流的一些参数,例如分辨率、帧率、编码格式等。然后,我们使用 avcodec_open2 函数打开了该编码器,并使用 avcodec_parameters_from_context 函数将编码器参数复制到视频流的 codecpar 结构体中。最后,我们打开输出文件并写入文件头,然后循环编码 YUV 数据并将编码后的数据写入文件中,最后写入文件尾并释放资源。
需要注意的是,在编码每一帧数据时,我们需要将 AVFrame 转换成编码器所需的格式,并使用 avcodec_send_frame 函数发送该 AVFrame,然后使用 avcodec_receive_packet 函数接收编码器编码后的数据。在将编码后的数据写入文件前,需要将编码后的时间戳 PTS 和 DTS 进行适当的转换和设置。
阅读全文