内存IO模式
- AVIOContext *avio_alloc_context(
- unsigned char *buffer,
- int buffer_size,
- int write_flag,
- void *opaque,
- int (*read_packet)(void *opaque, uint8_t *buf, int buf_size),
- int (*write_packet)(void *opaque, uint8_t *buf, int buf_size),
- int64_t (*seek)(void *opaque, int64_t offset, int whence)
- );
复制代码 参数阐明:
- opaque是 read_packet / write_packet 的第⼀个参数,指向⽤户数据。
- buffer和buffer_size是 read_packet / write_packet 的第⼆个和第三个参数,是供FFmpeg使⽤的数据区。
- buffer ⽤作FFmpeg输⼊时,由⽤户负责向 buffer 中填充数据,FFmpeg取⾛数据。
- buffer ⽤作FFmpeg输出时,由FFmpeg负责向 buffer 中填充数据,⽤户取⾛数据。
- write_flag是缓冲区读写标记,读写的主语是指FFmpeg。
- write_flag 为1时, buffer ⽤于写,即作为FFmpeg输出。
- write_flag 为0时, buffer ⽤于读,即作为FFmpeg输⼊。
- read_packet和write_packet是函数指针,指向⽤户编写的回调函数。
- seek也是函数指针,需要⽀持seek时使⽤。 可以类⽐fseek的机制
一、avio_alloc_context 的环形缓冲本质
avio_alloc_context 创建的 AVIOContext 结构体 内部维护了一个环形缓冲区,其焦点特性包括:
- 循环存储机制
- 缓冲区逻辑上首尾相连,数据写入时自动回绕(Wrap-Around),制止频仍内存分配。
- 比方,当缓冲区写满后,新数据会覆盖最早写入的数据(取决于设置)。
- 双指针管理
- 读指针(buf_ptr):指向当前读取位置。
- 写指针(buf_end):指向当前写入位置。
- 通过模运算(%)实现指针的循环移动,比方:
buf_ptr = (buf_ptr + size) % buffer_size。
- 缓冲与性能优化
- 预分配固定巨细的内存(如 4KB、8KB),减少系统调用次数。
- 实用于网络流、内存数据流等需要一连读写的场景。
实现流程
准备文件
在build路径下准备相干mp3和aac文件
添加main函数参数,表示输入文件和输出文件
打开文件
使用FILE二进制打开输入文件和输出文件
- const char *in_file_name = argv[1];
- const char *out_file_name = argv[2];
- FILE *in_file = NULL;
- FILE *out_file = NULL;
- // 1. 打开参数文件
- in_file = fopen(in_file_name, "rb");
- if(!in_file) {
- printf("open file %s failed\n", in_file_name);
- return -1;
- }
- out_file = fopen(out_file_name, "wb");
- if(!out_file) {
- printf("open file %s failed\n", out_file_name);
- return -1;
- }
复制代码 自定义IO读取
- 为AVFormatContex添加自定义读取规则,即自己实现一个AVIOContext,而不是使用默认的
- AVIOContext使用的是环形缓冲区,即缓冲区满的时间覆盖前面的缓冲区
- 需要设置环形缓冲区的巨细、文件指针、以及缓冲内存回调函数,在内存数据读完的时间触发回调函数,继续从文件读取数据到环形缓冲区
- uint8_t *io_buffer = av_malloc(BUF_SIZE);
- AVIOContext *avio_ctx = avio_alloc_context(io_buffer, BUF_SIZE, 0, (void *)in_file, \
- read_packet, NULL, NULL);
- AVFormatContext *format_ctx = avformat_alloc_context();
- format_ctx->pb = avio_ctx;
- int ret = avformat_open_input(&format_ctx, NULL, NULL, NULL);
- if(ret < 0) {
- printf("avformat_open_input failed:%s\n", av_err2str(ret));
- return -1;
- }
复制代码 read_packet回调函数
- static int read_packet(void *opaque, uint8_t *buf, int buf_size)
- {
- FILE *in_file = (FILE *)opaque;
- int read_size = fread(buf, 1, buf_size, in_file);
- // printf("read_packet read/*_*/size:%d, buf_size:%d\n", read_size, buf_size);
- if(read_size <=0) {
- return AVERROR_EOF; // 数据读取完毕
- }
- return read_size;
- }
复制代码 查找解码器
- 根据ID查找解码器,这里直接查找AAC解码器
- 分配解码器上下文,将解码器绑定到上下文中
- AVCodecContext *codec_ctx = avcodec_alloc_context3(codec);
- if(!codec_ctx) {
- printf("avcodec_alloc_context3 failed\n");
- return -1;
- }
- ret = avcodec_open2(codec_ctx, codec, NULL);
- if(ret < 0) {
- printf("avcodec_open2 failed:%s\n", av_err2str(ret));
- return -1;
- }
复制代码 解码并写入文件
- AVPacket *packet = av_packet_alloc();
- ret = av_read_frame(format_ctx, packet);
复制代码
- ret = avcodec_send_packet(dec_ctx, packet);
复制代码
- ret = avcodec_receive_frame(dec_ctx, frame);
复制代码
- int data_size = av_get_bytes_per_sample(dec_ctx->sample_fmt);
- for(int i = 0; i < frame->nb_samples; i++) {
- for(int ch = 0; ch < dec_ctx->channels; ch++) {
- fwrite(frame->data[ch] + data_size *i, 1, data_size, outfile);
- }
- }
复制代码
- while (1) {
- ret = av_read_frame(format_ctx, packet);
- if(ret < 0) {
- printf("av_read_frame failed:%s\n", av_err2str(ret));
- break;
- }
- decode(codec_ctx, packet, frame, out_file);
- }
复制代码 decode函数
- static void decode(AVCodecContext *dec_ctx, AVPacket *packet, AVFrame *frame, FILE *outfile){ int ret = 0; ret = avcodec_send_packet(dec_ctx, packet);
- if(ret == AVERROR(EAGAIN)) { printf("Receive_frame and send_packet both returned EAGAIN, which is an API violation.\n"); } else if(ret < 0) { printf("Error submitting the packet to the decoder, err:%s\n", av_get_err(ret)); return; } while (ret >= 0) { ret = avcodec_receive_frame(dec_ctx, frame);
- if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) { return; } else if (ret < 0) { printf("Error during decoding\n"); exit(1); } if(!packet) { printf("get flush frame\n"); } int data_size = av_get_bytes_per_sample(dec_ctx->sample_fmt); // print_sample_format(frame); /** P表示Planar(平面),其数据格式分列方式为 : LLLLLLRRRRRRLLLLLLRRRRRRLLLLLLRRRRRRL...(每个LLLLLLRRRRRR为一个音频帧) 而不带P的数据格式(即交织分列)分列方式为: LRLRLRLRLRLRLRLRLRLRLRLRLRLRLRLRLRLRL...(每个LR为一个音频样本) 播放范例: ffplay -ar 48000 -ac 2 -f f32le believe.pcm 并不是每一种都是这样的格式 */ // 这里的写法不是通用,通用要调用重采样的函数去实现 // 这里只是针对解码出来是planar格式的转换 for(int i = 0; i < frame->nb_samples; i++) { for(int ch = 0; ch < dec_ctx->channels; ch++) { fwrite(frame->data[ch] + data_size *i, 1, data_size, outfile); } } }}
复制代码 冲刷解码器
- decode(codec_ctx, NULL, frame, out_file);
复制代码 竣事利用
退出之前要开释内存、关闭文件
- fclose(in_file);
- fclose(out_file);
- av_free(io_buffer);
- av_frame_free(frame);
- av_packet_free(packet);
- avformat_close_input(&format_ctx);
- avcodec_free_context(&codec_ctx);
复制代码 更多资料:https://github.com/0voice
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |