ffmpeg2----API编程1

本文详细介绍了音频和视频的解码、编码流程,包括使用FFmpeg库的相关函数,如avcodec_find_decoder、avcodec_alloc_context3、avcodec_send_packet等。此外,还阐述了音视频封装的过程,涉及avformat_open_input、avformat_write_header等关键步骤。内容适合于熟悉FFmpeg的开发者参考。
摘要由CSDN通过智能技术生成

一  音视频解码流程

1.音频解码

相关头文件

#include <libavutil/frame.h>
#include <libavutil/mem.h>
#include <libavcodec/avcodec.h>

解码流程

1. 初始化AVPacket 结构体

AVPacket *av_packet_alloc(void); 注:老的接口在开始解码钱需要调用avcodec_resgister_all 注册编码器

2.根据ID查找对应的编码器

AVCodec *avcodec_find_decoder(enum AVCodecID id);

3.使用编码器ID舒适化数据流解析上下文结构体AVCodecParserContext

该结构体可用于从连续的数据流中分理处一帧帧为解码的数据,若要解码的对象无需此操作,可跳过

AVCodecParserContext *av_parser_init(int codec_id);

4.为编码器分配空间,打开编码器

AVCodecContext *avcodec_alloc_context3(const AVCodec *codec);

int avcodec_open2(AVCodecContext *avctx, const AVCodec *codec, AVDictionary **options);

5.为数据帧结构体分配空间

AVFrame *av_frame_alloc(void);

6.打开输入输出

7.从数据流中取出一个AVPacket

此处填入存放数据流的指针,取出一个packet后,下次在该数据流指针的基础上加上偏移----此次取出的数据大小,即函数返回值,传入的数据流大小也要减去该偏移,当剩余数据流大小小于预期(4096)时,及时从输入读取数据,并将本次数据置于新读取的数据前端。

int av_parser_parse2(AVCodecParserContext *s, AVCodecContext *avctx, uint8_t **poutbuf, int *poutbuf_size, const uint8_t *buf, int buf_size, int64_t pts, int64_t dts, int64_t pos);

8.将数据包送入解码器

packet得data要自己分配空间

int avcodec_send_packet(AVCodecContext *avctx, const AVPacket *avpkt)

9.接收解码后的数据

解码后的数据使用while循环接收,直至所有数据被接收完成

int  avcodec_receive_frame(AVCodecContext *avctx, AVFrame *frame);

10.获取本次采样数据的大小

int av_get_bytes_per_sample(enum AVSampleFormat sample_fmt)

11.处理解码后的数据

7-11重复执行,只要所有数据被解码完毕

12.冲刷解码缓冲区

执行8-11,但是不传入数据

13.释放资源

关闭输入/输出,释放5 4 3 1中申请的资源,注意逆序释放

av_frame_free(&decoded_frame);
avcodec_free_context(&c);
av_parser_close(parser);
av_packet_free(&pkt);

2.视频解码

流程同音频解码

二 音视频编码流程

1.音频编码

相关头文件

#include <libavcodec/avcodec.h>
#include <libavutil/channel_layout.h>
#include <libavutil/common.h>
#include <libavutil/frame.h>
#include <libavutil/samplefmt.h>

编码流程

1.查找编码器

AVCodec *avcodec_find_encoder(enum AVCodecID id);

2. 为编码器上下文分配空间

AVCodecContext *avcodec_alloc_context3(const AVCodec *codec);

3.设置编码器参数

c->bit_rate = 64000;
c->sample_fmt = AV_SAMPLE_FMT_S16;

4.检查当前格式是否被编码器支持

使用下列函数检查当前格式是否被编码器支持

static int check_sample_fmt(const AVCodec *codec, enum AVSampleFormat sample_fmt)
{
    const enum AVSampleFormat *p = codec->sample_fmts;

    while (*p != AV_SAMPLE_FMT_NONE) {
        if (*p == sample_fmt)
            return 1;
        p++;
    }
    return 0;
}

5.设置音频解码器参数

实际使用过程中, 均指定为真实参数

设置合适的频率,示例给出的是在所支持的频率中找出最接近44100的一个,

c->sample_rate    = select_sample_rate(codec);
static int select_sample_rate(const AVCodec *codec)
{
    const int *p;
    int best_samplerate = 0;

    if (!codec->supported_samplerates)
        return 44100;

    p = codec->supported_samplerates;
    while (*p) {
        if (!best_samplerate || abs(44100 - *p) < abs(44100 - best_samplerate))
            best_samplerate = *p;
        p++;
    }
    return best_samplerate;
}

设置通道布局

c->channel_layout = select_channel_layout(codec);
//根据音频布局选择通道
c->channels       = av_get_channel_layout_nb_channels(c->channel_layout);

static int select_channel_layout(const AVCodec *codec)
{
    const uint64_t *p;
    uint64_t best_ch_layout = 0;
    int best_nb_channels   = 0;

    if (!codec->channel_layouts)
        return AV_CH_LAYOUT_STEREO;

    p = codec->channel_layouts;
    while (*p) {
        int nb_channels = av_get_channel_layout_nb_channels(*p);

        if (nb_channels > best_nb_channels) {
            best_ch_layout    = *p;
            best_nb_channels = nb_channels;
        }
        p++;
    }
    return best_ch_layout;
}

 

6. 打开编码器

7. 初始化AVPacket 结构体

AVPacket *av_packet_alloc(void);

8.为AVFrame分配空间

AVFrame *av_frame_alloc(void); 

9.设置frame参数

frame->nb_samples     = c->frame_size;
frame->format         = c->sample_fmt;
frame->channel_layout = c->channel_layout;

10. 为视频音频数据分配新的缓冲区

一定要在8之后执行该步骤,第二个参数为字节对齐,官方例程给的为32

int av_frame_get_buffer(AVFrame *frame, int align);

11. 确保frame可写

当不可写时,会为该frame重新分配空间并拷贝

int av_frame_make_writable(AVFrame *frame);

12.发送数据帧

填充frame->data,其他参数保持之前的不变

int avcodec_send_frame(AVCodecContext *avctx, const AVFrame *frame);

13.接收编码后的数据帧

int avcodec_receive_packet(AVCodecContext *avctx, AVPacket *avpkt);

接收完数据后释放当前帧得数据,但保持其他参数不变

void av_packet_unref(AVPacket *pkt);

14.处理编码后得数据

重复执行11倒14,直至所有数据被处理完毕

15.冲刷缓冲区

不发送数据帧 步骤11 ,直接读取编码帧 步骤12

16.释放申请过得资源

逆序释放申请过得资源

avcodec_free_context(&c);

av_frame_free(&frame);

av_packet_free(&pkt);

2.视频编码

相关头文件

#include <libavcodec/avcodec.h>
#include <libavutil/opt.h>
#include <libavutil/imgutils.h>

编码流程

1. 查找编码器

可以通过名字或者编码器ID来查找编码器

AVCodec *avcodec_find_encoder_by_name(const char *name);

2.为编码器分配上下文存储空间

AVCodecContext *avcodec_alloc_context3(const AVCodec *codec);

3.初始化AVPacket 结构体

AVPacket *av_packet_alloc(void);

4. 设置编码器参数

c->bit_rate = 400000;
c->width = 352;
c->height = 288;
c->time_base = (AVRational){1, 25};
c->framerate = (AVRational){25, 1};
c->gop_size = 10;
c->max_b_frames = 1;
c->pix_fmt = AV_PIX_FMT_YUV420P;
// h264 码流可以设置预设参数
if (codec->id == AV_CODEC_ID_H264)
    av_opt_set(c->priv_data, "preset", "slow", 0);

5. 打开编码器

int avcodec_open2(AVCodecContext *avctx, const AVCodec *codec, AVDictionary **options);

6. 为AVFrame分配空间

AVFrame *av_frame_alloc(void);

7. 设置frame参数

    frame->format = c->pix_fmt;
    frame->width  = c->width;
    frame->height = c->height;

8.  为视频音频数据分配新的缓冲区

一定要在7之后执行该步骤,第二个参数为字节对齐,官方例程给的为32

int av_frame_get_buffer(AVFrame *frame, int align);

9. 确保frame可写

当不可写是,会为该frame重新分配空间并拷贝

int av_frame_make_writable(AVFrame *frame);

10.设置pts

frame->pts = i;

11. 向编码器发送数据帧

填充frame->data; 其他参数保持前面步骤的填写

int avcodec_send_frame(AVCodecContext *avctx, const AVFrame *frame);

12. 接收编码后的数据帧

int avcodec_receive_packet(AVCodecContext *avctx, AVPacket *avpkt);

接收完数据后释放当前帧得数据,但保持其他参数不变

void av_packet_unref(AVPacket *pkt);  此接口在当前版本中无需调用,在avcodec_receive_packet接口中会调用该接口

13. 处理编码后得数据

重复执行10倒13,直至所有数据被处理完毕

14. 冲刷缓冲区

不发送数据帧----步骤11 ,直接读取编码帧----步骤12

15. 释放申请过得资源

逆序释放申请过得资源

avcodec_free_context(&c);
av_frame_free(&frame);
av_packet_free(&pkt);

四 音视频解封装

相关头文件

#include <libavutil/timestamp.h>
#include <libavformat/avformat.h>

封装流程

1. 根据具输出文件格式申请AVFormatContext

int avformat_alloc_output_context2(AVFormatContext **ctx, ff_const59 AVOutputFormat *oformat, const char *format_name, const char *filename);

后面三个成员根据实际情况填一个就行

2. 添加音视频流,即为音视频流申请AVstream

第二个参数为编码器参数,如果编码器确定,可将该参数设为NULL,然后自行添加i编码器参数

AVStream *avformat_new_stream(AVFormatContext *s, const AVCodec *c);

s->nb_streams-1 为该流的ID即返回结构体得AVStream的成员Id的值

通常情况下c填NULL,手动对AVStream 结构体中的codecpar进行赋值,

或从codecContex中复制

3. 设置该音视频流得参数

设置音视频流得参数时有两种方式,

1)从编码器拷贝

int avcodec_parameters_from_context(AVCodecParameters *par, const AVCodecContext *codec);

2)手动赋值

音频:

Stream->codecpar->codec_id			= eAudioCodecID;
Stream->codecpar->codec_type		= AVMEDIA_TYPE_AUDIO;
Stream->codecpar->sample_rate 		= pAudioParam->nSampleRate;
Stream->codecpar->channels	  		= pAudioParam->nChannelNum;
Stream->codecpar->channel_layout	= AV_CH_LAYOUT_MONO;  //双声道用AV_CH_LAYOUT_STEREO
Stream->codecpar->frame_size = AUDIO_ENCODE_FRAME_SIZE;
Stream->time_base.den	= pAudioParam->nSampleRate;
Stream->time_base.num	= 1;
//aac 需要在extradata中加入音频参数的头
char chAudioConfig[2];
char nRateIndex = 0;
char nAudioAACType = FF_AAC_PROFILE_ID;
for (nRateIndex = 0; nRateIndex < 16; nRateIndex++)
	if (g_AudioSampleRates[nRateIndex] == pAudioParam->nSampleRate)
		break;
if (nRateIndex == 16) {
	ERROR_LOG("nRateIndex = %d \n",nRateIndex);
}
chAudioConfig[0] = (nAudioAACType<<3) | (nRateIndex>>1);
chAudioConfig[1] = (nRateIndex<<7) | (pAudioParam->nChannelNum<<3);
pStAudioStream->codecpar->extradata      = (uint8_t *)av_mallocz(2);
memcpy(pStAudioStream->codecpar->extradata, chAudioConfig, 2);
pStAudioStream->codecpar->extradata_size= 2;

视频:

此处注意提取sps和pps的方法

stream->codecpar->codec_id		  = AV_CODEC_ID_H264;
stream->codecpar->width				= pVideoParam->nWidth;
stream->codecpar->height			= pVideoParam->nHeight;
stream->codecpar->codec_type	= AVMEDIA_TYPE_VIDEO;
stream->codecpar->bit_rate			= pVideoParam->nKBps*1024;
stream->time_base.den				   = VIDEO_TIME_BASE_AV;
stream->time_base.num				  = 1;
stream->index						          = VIDEO_STREAM_INDEX;  //可以由2中分配 待确认 与			AVFormatContext中得对应
//同时在视频帧中提取sps和pps信息,存入extradata中
streams->codecpar->extradata      = (uint8_t *)av_mallocz(nSpsPpsLen);
memcpy(streams->codecpar->extradata, pFrameData, nSpsPpsLen);
streams->codecpar->extradata_size = nSpsPpsLen;	

4. 打开输出文件

int avio_open(AVIOContext **s, const char *url, int flags);

s: 填写AVFormatContext中得oc->pb

url: 输出文件得名字

flags: 写AVIO_FLAG_WRITE,读AVIO_FLAG_READ,读写:AVIO_FLAG_READ_WRITE

5.写文件头

int avformat_write_header(AVFormatContext *s, AVDictionary **options);

6.交叉写入音视频帧

比较音视频时间戳的时间(由调用者控制交叉写入时需要该接口):

int av_compare_ts(int64_t ts_a, AVRational tb_a, int64_t ts_b, AVRational tb_b);

根据需要转换时基

void av_packet_rescale_ts(AVPacket *pkt, AVRational tb_src, AVRational tb_dst);

pkt->stream_index = st->index; // 设置当前要写入pkt的流的index,决定了当前是音频还是视频

写入数据帧(调用该接口由ffmepg负责影视频得交叉写入,av_write_frame由写入者控制)

int av_interleaved_write_frame(AVFormatContext *s, AVPacket *pkt);

7. 写入文件尾

int av_write_trailer(AVFormatContext *s);

8.关闭当前打开得资源

    if (have_video)
        close_stream(oc, &video_st);
    if (have_audio)
        close_stream(oc, &audio_st);

    if (!(fmt->flags & AVFMT_NOFILE))
        /* Close the output file. */
        avio_closep(&oc->pb);

    /* free the stream */
    avformat_free_context(oc);

四 音视频解封装

相关头文件

#include <libavutil/timestamp.h>
#include <libavformat/avformat.h>

解封装流程

1.打开输入输入

int avformat_open_input(AVFormatContext **ps, const char *url, ff_const59 AVInputFormat *fmt, AVDictionary **options);

2.查找输入中得流得信息

int avformat_find_stream_info(AVFormatContext *ic, AVDictionary **options);

3.读取数据流

数据流读取厚更具pkt->stream_index来判断当前读取得数据包类型

int av_read_frame(AVFormatContext *s, AVPacket *pkt);

4.处理读取出来得数据

5.关闭已打开得资源

转封装注意事项

转封装时注意原文件中可能存在多个视频流,注意根据原视频流中得stream->codecpar->codec_type进行合理剔除

 

知乎

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值