Skip to main content

音视频

参数说明

  • 码率:
  • 分辨率:
  • 帧率:
  • 编码格式:
  • 帧类型: 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);
}

音频重采样 swr_convert

对应的模块 libswresample 以前使用的libavresample模块已经过时

视频图像缩放和颜色空间转换 sws_scale

对应的模块 libswscale

音视频编码的三个重要结构体

  • AVStream
  • AVCodecContext
  • AVCodec

根据codec_id 可以找到 AVCodec *codec = avcodec_find_encoder(codec_id)
根据AVCodec可以申请 AVCodecContext *c = avcodec_alloc_context3(codec)
AVStream 通过 avformat_new_stream 创建
AVStream 需要保存 codec_id
st->id = codec_id;
st->time_base = 时间基准
avcodec_parameters_from_context(st->codecpar, c);

关于一些方法

ffmpeg提供av_rescale_q函数用于time_base之间转换,av_rescale_q(a,b,c)作用相当于执行a*b/c,通过设置b,c的值,可以很方便的实现time_base之间转换