音视频
参数说明
- 码率:
- 分辨率:
- 帧率:
- 编码格式:
- 帧类型: I帧 B帧 P帧
- 声道
- 采样率
- 采样深度
- 采样格式
- DTS
- PTS
- 参考时钟
ffmpeg将视频保存到文件
使用ffmpeg库,将音频和视频混淆到一个文件,通常需要几个步骤
// a wrapper around a single output AVStream
typedef struct OutputStream {
AVStream *st;
AVCodecContext *enc;
/* pts of the next frame that will be generated */
int64_t next_pts;
int samples_count;
AVFrame *frame;
AVFrame *tmp_frame;
float t, tincr, tincr2;
struct SwsContext *sws_ctx;
struct SwrContext *swr_ctx;
} OutputStream;
1.使用 avformat_alloc_output_context2,创建一个AVFormatContenxt(oc) 上下文对象
AVFormatContext *oc=NULL;
avformat_alloc_output_context2(&oc, NULL, NULL, filename);
传入的后两个参数为,编码格式和文件名称,函数会根据输入自动适配相应的音频和视频编码格式. 2.生成的AVFormatContext的对象,包含一个 AVOutputFormat(oc->fmt) 对象的指针,里面有分配或者知道的编码格式 fmt->video_codec 和 fmt->audio_codec ,根据编码器ID生成指定的流信息
OutputStream video_st = { 0 }, audio_st = { 0 };
AVCodec *audio_codec, *video_codec;
AVOutputFormat* fmt = oc->oformat;
if (fmt->video_codec != AV_CODEC_ID_NONE)
add_stream(&video_st, oc, &video_codec, fmt->video_codec);
if (fmt->audio_codec != AV_CODEC_ID_NONE)
add_stream(&audio_st, oc, &audio_codec, fmt->audio_codec);
3.创建了音频流和视频流之后,需要打开音频编码器和视频编码器
AVDictionary *opt = NULL;
open_video(oc, video_codec, &video_st, opt);
open_audio(oc, audio_codec, &audio_st, opt);
可以使用opt设置需要的选项 4.使用 avio_open 打开要输出的文件
5.使用 avformat_write_header 写入文件头,或者相关的输出的一个概要信息
avformat_write_header(oc, &opt);
6.文件头或者概要信息生成之后,不停的写入音频帧和视频帧
if (!(fmt->flags & AVFMT_NOFILE)) {
ret = avio_open(&oc->pb, filename, AVIO_FLAG_WRITE);
if (ret < 0) {
fprintf(stderr, "Could not open '%s': %s\n", filename,
av_err2str(ret));
return 1;
}
}
7.使用 av_write_trailer 写入文件结尾
av_write_trailer(oc);
8.关闭音频流、视频流、关闭文件,上下文资源
close_stream(oc, &video_st);
close_stream(oc, &audio_st);
avio_closep(&oc->pb);
avformat_free_context(oc);
打开音视频流的步骤
void add_stream(OutputStream *ost, AVFormatContext *oc,AVCodec **codec,enum AVCodecID codec_id) 1.根据AVFormat上下文oc 创建一个流对象 AVStream* st
AVStream* st = avformat_new_stream(oc, NULL);
2、根据编码ID找到相应的编码器
*codec = avcodec_find_encoder(codec_id);
3.确保以上两部都能成功时,分配流ID,并根据编码器创建编码器上下文
st->id = oc->nb_streams-1;
AVCodecContext *c = avcodec_alloc_context3(*codec);
ost->enc = c;
4.根据编码器的类型 AVMEDIA_TYPE_AUDIO 或者 AVMEDIA_TYPE_VIDEO 设置相应的参数编解码上下文的参数 音频 c->sample_fmt 采样格式 c->bit_rate 比特率 c->sample_rate 采样率 c->time_base 时间基准 视频 codec_id bit_rate width height time_base gop_size pix_fmt max_b_frames //MPEG2VIDEO mb_decision //MPEG1VIDEO 5.一些额外的配置
/* Some formats want stream headers to be separate. */
if (oc->oformat->flags & AVFMT_GLOBALHEADER)
c->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
申请音频帧的方法和需要设置的参数
- format:采样格式
- channel_layout:声道layout
- sample_rate:采样率
- nb_samples:采样个数
AVFrame *alloc_audio_frame(enum AVSampleFormat sample_fmt,
uint64_t channel_layout,
int sample_rate, int nb_samples)
{
AVFrame *frame = av_frame_alloc();
int ret;
if (!frame) {
fprintf(stderr, "Error allocating an audio frame\n");
exit(1);
}
frame->format = sample_fmt;
frame->channel_layout = channel_layout;
frame->sample_rate = sample_rate;
frame->nb_samples = nb_samples;
if (nb_samples) {
ret = av_frame_get_buffer(frame, 0);
if (ret < 0) {
fprintf(stderr, "Error allocating an audio buffer\n");
exit(1);
}
}
return frame;
}
申请视频帧的方法和需要设置的参数
- pix_fmt
- width
- height
static AVFrame *alloc_picture(enum AVPixelFormat pix_fmt, int width, int height)
{
AVFrame *picture;
int ret;
picture = av_frame_alloc();
if (!picture)
return NULL;
picture->format = pix_fmt;
picture->width = width;
picture->height = height;
/* allocate the buffers for the frame data */
ret = av_frame_get_buffer(picture, 32);
if (ret < 0) {
fprintf(stderr, "Could not allocate frame data.\n");
exit(1);
}
return picture;
}
视频帧格式转换
if (c->pix_fmt != AV_PIX_FMT_YUV420P) {
/* as we only generate a YUV420P picture, we must convert it
* to the codec pixel format if needed */
if (!ost->sws_ctx) {
ost->sws_ctx = sws_getContext(c->width, c->height,
AV_PIX_FMT_YUV420P,
c->width, c->height,
c->pix_fmt,
SCALE_FLAGS, NULL, NULL, NULL);
if (!ost->sws_ctx) {
fprintf(stderr,"Could not initialize the conversion context\n");
exit(1);
}
}
fill_yuv_image(ost->tmp_frame, ost->next_pts, c->width, c->height);
sws_scale(ost->sws_ctx, (const uint8_t * const *) ost->tmp_frame->data,
ost->tmp_frame->linesize, 0, c->height, ost->frame->data,
ost->frame->linesize);
} else {
fill_yuv_image(ost->frame, ost->next_pts, c->width, c->height);
}