使用ffmpeg接口将YUV编码封装为文件
来源:互联网 发布:数据架构师 编辑:程序博客网 时间:2024/05/16 10:09
目录
- 1. 第一步: 初始化相关结构体
- 1.1. 第一步: 手动创造 AVFormatContext(封装输出文件格式)
- 1.2. 第二步: 设置stream的id和codec等参数
- 1.3. 第三步: 打开编码音视频数据所需要的编码器
- 1.4. 第四步: 分配相应的frame对象 为frame分配空间
- 1.5. 打开音视频,分配frame对象的代码
- 2. 第二步: 打开输出文件并写入文件头
- 3. 第三步: 编码和封装循环
- 3.1. 第一步: 读取原始视频数据
- 3.1.1. 第一步: 根据时长判断是否需要继续进行处理、读取视频到AVFrame和设置pts。其中时长判断部分根据pts和AVCodecContext的time_base判断。实现如下:
- 3.1.2. 第二步: 读取视频到AVFrame我们定义一个fill_yuv_image函数实现:
- 3.1.3. 第三步: 然后进行pts的设置,很简单,就是上一个frame的pts递增1:
- 3.1.4. 整个获取视频信号的实现如:
- 3.2. 第二步: 编码
- 3.3. 第三步: 写出编码后的数据到输出视频文件
- 3.4. 写入文件尾,并进行收尾工作
- 3.1. 第一步: 读取原始视频数据
第一步: 初始化相关结构体
第一步: 手动创造 AVFormatContext(封装输出文件格式)
第二步: 设置stream的id和codec等参数
AVStream::codec是一个AVCodecContext类型的指针变量成员,设置其中的值可以对编码进行配置。整个添加stream的例子如:
* Add an output stream. *
static void add_stream(OutputStream *ost, AVFormatContext *oc, AVCodec **codec, enum AVCodecID codec_id)
{
AVCodecContext *c;
int i;
* find the encoder *
*codec = avcodec_find_encoder(codec_id);
if (!(*codec))
{
fprintf(stderr, “Could not find encoder for ‘%s’\n”, avcodec_get_name(codec_id));
exit(1);
}
ost->st = avformat_new_stream(oc, *codec);
if (!ost->st)
{
fprintf(stderr, “Could not allocate stream\n”);
exit(1);
}
ost->st->id = oc->nb_streams - 1;
c = ost->st->codec;
switch ((*codec)->type)
{
case AVMEDIA_TYPE_AUDIO:
c->sample_fmt = (*codec)->sample_fmts ? (*codec)->sample_fmts[0] : AV_SAMPLE_FMT_FLTP;
c->bit_rate = 64000;
c->sample_rate = 44100;
if ((*codec)->supported_samplerates)
{
c->sample_rate = (*codec)->supported_samplerates[0];
for (i = 0; (*codec)->supported_samplerates[i]; i++)
{
if ((*codec)->supported_samplerates[i] == 44100)
c->sample_rate = 44100;
}
}
c->channels = av_get_channel_layout_nb_channels(c->channel_layout);
c->channel_layout = AV_CH_LAYOUT_STEREO;
if ((*codec)->channel_layouts)
{
c->channel_layout = (*codec)->channel_layouts[0];
for (i = 0; (*codec)->channel_layouts[i]; i++)
{
if ((*codec)->channel_layouts[i] == AV_CH_LAYOUT_STEREO)
c->channel_layout = AV_CH_LAYOUT_STEREO;
}
}
c->channels = av_get_channel_layout_nb_channels(c->channel_layout);
{
AVRational r = { 1, c->sample_rate };
ost->st->time_base = r;
}
break;
case AVMEDIA_TYPE_VIDEO:
c->codec_id = codec_id;
c->bit_rate = 400000;
* Resolution must be a multiple of two. *
c->width = 352;
c->height = 288;
/* timebase: This is the fundamental unit of time (in seconds) in terms
- of which frame timestamps are represented. For fixed-fps content,
- timebase should be 1/framerate and timestamp increments should be
- identical to 1. */
{
AVRational r = { 1, STREAM_FRAME_RATE };
ost->st->time_base = r;
}
c->time_base = ost->st->time_base;
c->gop_size = 12; * emit one intra frame every twelve frames at most *
c->pix_fmt = AV_PIX_FMT_YUV420P;
if (c->codec_id == AV_CODEC_ID_MPEG2VIDEO)
{
* just for testing, we also add B frames *
c->max_b_frames = 2;
}
if (c->codec_id == AV_CODEC_ID_MPEG1VIDEO)
{
/* Needed to avoid using macroblocks in which some coeffs overflow.
- This does not happen with normal video, it just happens here as
the motion of the chroma plane does not match the luma plane. */
c->mb_decision = 2;
}
break;
default:
break;
}
*\* Some formats want stream headers to be separate. \** if (oc->oformat->flags & AVFMT\_GLOBALHEADER) c->flags |= AV\_CODEC\_FLAG\_GLOBAL\_HEADER;
}
第三步: 打开编码音视频数据所需要的编码器
打开编码器如之前一样,调用avcodec_open函数
第四步: 分配相应的frame对象 为frame分配空间
打开音视频,分配frame对象的代码
void Open_video(AVFormatContext *oc, AVCodec *codec, OutputStream *ost, AVDictionary *opt_arg, IOParam &io)
{
int ret;
AVCodecContext *c = ost->st->codec;
AVDictionary *opt = NULL;
av_dict_copy(&opt, opt_arg, 0);
* open the codec *
ret = avcodec_open2(c, codec, &opt);
av_dict_free(&opt);
if (ret < 0)
{
fprintf(stderr, “Could not open video codec: %d\n”, ret);
exit(1);
}
* allocate and init a re-usable frame *
ost->frame = alloc_picture(c->pix_fmt, c->width, c->height);
if (!ost->frame)
{
fprintf(stderr, “Could not allocate video frame\n”);
exit(1);
}
/* If the output format is not YUV420P, then a temporary YUV420P
- picture is needed too. It is then converted to the required
- output format. */
ost->tmp_frame = NULL;
if (c->pix_fmt != AV_PIX_FMT_YUV420P)
{
ost->tmp_frame = alloc_picture(AV_PIX_FMT_YUV420P, c->width, c->height);
if (!ost->tmp_frame)
{
fprintf(stderr, “Could not allocate temporary picture\n”);
exit(1);
}
}
//打开输入YUV文件 fopen\_s(&g\_inputYUVFile, io.input\_file\_name, "rb+"); if (g\_inputYUVFile == NULL) { fprintf(stderr, "Open input yuv file failed.\n"); exit(1); }
}
第二步: 打开输出文件并写入文件头
如果判断需要写出文件的话,则需要打开输出文件。在这里,我们可以不再定义输出文件指针,并使用fopen打开,而是直接使用FFMpeg的API——avio_open来实现输出文件的打开功能。该函数的声明如下:
int avio_open(AVIOContext **s, const char *url, int flags);
该函数的输入参数为:
s:输出参数,返回一个AVIOContext;如果打开失败则返回NULL;
url:输出的url或者文件的完整路径;
flags:控制文件打开方式,如读方式、写方式和读写方式;
实际的代码实现方式如下:
* open the output file, if needed *
if (!(fmt->flags & AVFMT_NOFILE))// 没有打开文件,需要打开文件
{
ret = avio_open(&oc->pb, io.output_file_name, AVIO_FLAG_WRITE);
if (ret < 0)
{
fprintf(stderr, “Could not open ‘%s’: %d\n”, io.output_file_name, ret);
return 1;
}
}
写入文件头操作是生成视频文件中极为重要的一步,而实现过程却非常简单,只需要通过函数avformat_write_header即可,该函数的声明为:
int avformat_write_header(AVFormatContext *s, AVDictionary **options);
其输入参数实际上重要的只有第一个,即标记输出文件的句柄对象指针;options用于保存无法识别的设置项,可以传入一个空指针。其返回值表示写文件头成功与否,成功则返回0,失败则返回负的错误码。
实现方式如:
* Write the stream header, if any. *
ret = avformat_write_header(oc, &opt);
if (ret < 0)
{
fprintf(stderr, “Error occurred when opening output file: %d\n”,ret);
return 1;
}
第三步: 编码和封装循环
以视频流为例。编解码循环的过程实际上可以封装在一个函数Write_video_frame中。该函数从逻辑上可以分为3个部分:获取原始视频信号、视频编码、写入输出文件。
第一步: 读取原始视频数据
第一步: 根据时长判断是否需要继续进行处理、读取视频到AVFrame和设置pts。其中时长判断部分根据pts和AVCodecContext的time_base判断。实现如下:
AVCodecContext *c = ost->st->codec;
* check if we want to generate more frames *
{
AVRational r = { 1, 1 };
if (av_compare_ts(ost->next_pts, ost->st->codec->time_base, STREAM_DURATION, r) >= 0) // 当前帧的时间没有到达最大的时间间隔
{
return NULL;
}
}
第二步: 读取视频到AVFrame我们定义一个fill_yuv_image函数实现:
static void fill_yuv_image(AVFrame *pict, int frame_index, int width, int height)
{
int x, y, i, ret;
/* when we pass a frame to the encoder, it may keep a reference to it
- internally;
- make sure we do not overwrite it here
*/
ret = av_frame_make_writable(pict);
if (ret < 0)
{
exit(1);
}
i = frame_index;
* Y *
for (y = 0; y < height; y++)
{
ret = fread_s(&pict->data[0][y * pict->linesize[0]], pict->linesize[0], 1, width, g_inputYUVFile);
if (ret != width)
{
printf(“Error: Read Y data error.\n”);
exit(1);
}
}
* U *
for (y = 0; y < height / 2; y++)
{
ret = fread_s(&pict->data[1][y * pict->linesize[1]], pict->linesize[1], 1, width / 2, g_inputYUVFile);
if (ret != width / 2)
{
printf(“Error: Read U data error.\n”);
exit(1);
}
}
*\* V \** for (y = 0; y < height / 2; y++) { ret = fread\_s(&pict->data[2][y \* pict->linesize[2]], pict->linesize[2], 1, width / 2, g\_inputYUVFile); if (ret != width / 2) { printf("Error: Read V data error.\n"); exit(1); } }
}
第三步: 然后进行pts的设置,很简单,就是上一个frame的pts递增1:
ost->frame->pts = ost->next_pts++;
整个获取视频信号的实现如:
static AVFrame *get_video_frame(OutputStream *ost)
{
AVCodecContext *c = ost->st->codec;
* check if we want to generate more frames *
{
AVRational r = { 1, 1 };
if (av_compare_ts(ost->next_pts, ost->st->codec->time_base, STREAM_DURATION, r) >= 0)
{
return NULL;
}
}
fill_yuv_image(ost->frame, ost->next_pts, c->width, c->height);
ost->frame->pts = ost->next_pts++;
return ost->frame;
}
第二步: 编码
视频编码的方式同之前几次使用的方式相同,即调用avcodec_encode_video2,实现方法如:
* encode the image *
ret = avcodec_encode_video2(c, &pkt, frame, &got_packet);
if (ret < 0)
{
fprintf(stderr, “Error encoding video frame: %d\n”, ret);
exit(1);
}
第三步: 写出编码后的数据到输出视频文件
这部分的实现过程很简单,方式如下:
* rescale output packet timestamp values from codec to stream timebase *
av_packet_rescale_ts(pkt, *time_base, st->time_base);
pkt->stream_index = st->index;
* Write the compressed frame to the media file. *
// log_packet(fmt_ctx, pkt);
return av_interleaved_write_frame(fmt_ctx, pkt);
av_packet_rescale_ts函数的作用为不同time_base度量之间的转换,在这里起到的作用是将AVCodecContext的time_base转换为AVStream中的time_base。av_interleaved_write_frame函数的作用是写出AVPacket到输出文件。该函数的声明为:
int av_interleaved_write_frame(AVFormatContext *s, AVPacket *pkt);
该函数的声明也很简单,第一个参数是之前打开并写入文件头的文件句柄,第二个参数是写入文件的packet。返回值为错误码,成功返回0,失败则返回一个负值。
Write_video_frame函数的整体实现如:
int Write_video_frame(AVFormatContext *oc, OutputStream *ost)
{
int ret;
AVCodecContext *c;
AVFrame *frame;
int got_packet = 0;
AVPacket pkt = { 0 };
c = ost->st->codec;
frame = get_video_frame(ost);
av_init_packet(&pkt);
* encode the image *
ret = avcodec_encode_video2(c, &pkt, frame, &got_packet);
if (ret < 0)
{
fprintf(stderr, “Error encoding video frame: %d\n”, ret);
exit(1);
}
if (got_packet)
{
ret = write_frame(oc, &c->time_base, ost->st, &pkt);
}
else
{
ret = 0;
}
if (ret < 0)
{
fprintf(stderr, “Error while writing video frame: %d\n”, ret);
exit(1);
}
return (frame || got\_packet) ? 0 : 1;
}
以上是写入一帧视频数据的方法,写入音频的方法于此大同小异。整个编码封装的循环上层实现如:
while (encode_video || encode_audio)
{
* select the stream to encode *
if (encode_video && (!encode_audio || av_compare_ts(video_st.next_pts, video_st.st->codec->time_base, audio_st.next_pts, audio_st.st->codec->time_base) <= 0))
{// 允许写视频,&&不需要写音频||视频的进度落后于音频
encode_video = !Write_video_frame(oc, &video_st);
if (encode_video)
{
printf(“Write %d video frame.\n”, videoFrameIdx++);
}
else
{
printf(“Video ended, exit.\n”);
}
}
else
{
encode_audio = !Write_audio_frame(oc, &audio_st);
if (encode_audio)
{
printf(“Write %d audio frame.\n”, audioFrameIdx++);
}
else
{
printf(“Audio ended, exit.\n”);
}
}
}
写入文件尾,并进行收尾工作
写入文件尾的数据同写文件头一样简单,只需要调用函数av_write_trailer即可实现:
int av_write_trailer(AVFormatContext *s);
该函数只有一个参数即视频文件的句柄,当返回值为0时表示函数执行成功。
整个流程的收尾工作包括关闭文件中的数据流、关闭输出文件和释放AVCodecContext对象。其中关闭数据流的实现方式如:
void Close_stream(AVFormatContext *oc, OutputStream *ost)
{
avcodec_close(ost->st->codec);
av_frame_free(&ost->frame);
av_frame_free(&ost->tmp_frame);
sws_freeContext(ost->sws_ctx);
swr_free(&ost->swr_ctx);
}
关闭输出文件和释放AVCodecContext对象:
if (!(fmt->flags & AVFMT_NOFILE))
* Close the output file. *
avio_closep(&oc->pb);
* free the stream *
avformat_free_context(oc);
至此,整个处理流程便结束了。正确设置输入的YUV文件就可以获取封装好的音视频文件
- 使用ffmpeg接口将YUV编码封装为文件
- 使用ffmpeg接口将YUV编码为h.264
- 使用FFMPEG将YUV编码为H.264
- 使用ffmpeg接口将h.264解码为YUV
- ffmpeg将yuv封装为mp4测试代码
- 在Android上使用FFmpeg将摄像头采集的YUV裸流编码为h264。
- 用x264和ffmpeg将YUV编码为.h264(1)
- 用x264和ffmpeg将YUV编码为.h264(2)
- 在Andorid中使用FFmpeg实现YUV编码为MP4
- 使用ffmpeg接口解封装解码为YUV
- 使用ffmpeg将BMP图片编码为x264视频文件,将H264视频保存为BMP图片,yuv视频文件保存为图片的代码
- 使用ffmpeg将BMP图片编码为x264视频文件,将H264视频保存为BMP图片,yuv视频文件保存为图片的代码
- 使用ffmpeg实现解码并保存为yuv文件
- FFMpeg将YUV转码为H264
- 使用FFmpeg类库实现YUV视频序列编码为视频
- 使用FFmpeg类库实现YUV视频序列编码为视频
- 使用FFmpeg类库实现YUV视频序列编码为视频
- 使用FFmpeg类库实现YUV视频序列编码为视频
- wxWidgets无标题栏窗口的拖动
- Makefile初识
- Latex字体设置
- FTPrep, 99 Recover Binary Search Tree. TODO
- hahaha
- 使用ffmpeg接口将YUV编码封装为文件
- 模拟实现strstr函数功能
- poj2109 Power of Cryptography
- Qt界面美化1
- 3d模型
- 【精华】PS十大抠图技法(上)
- HIVE实战:官方案例练习
- Raspberry Pi 的應用
- python实战游戏开发——驾驶飞船