音视频学习(十八)——使用ffmepg实现视音频解码

news/2024/11/25 14:44:12/

视频解码

初始化

  1. 视频常用的编解码器id定义(以h264和h265为例)
// 定义在ffmpeg\include\libavcodec\avcodec.h
AV_CODEC_ID_H264
AV_CODEC_ID_H265
  1. 查找解码器:根据编解码id查看解码器
AVCodec* pCodecVideo = avcodec_find_decoder(codecID);
if (!pCodecVideo)
{printf("avcodec_find_decoder failed\n");return -1;
}
  1. 申请编码器上下文结构体内存,保存了视频编解码相关信息
AVCodecContext* pCodecCtxVideo = avcodec_alloc_context3(pCodecVideo);
if (!pCodecCtxVideo)
{printf("avcodec_alloc_context3 error\n");return -1;
}
  1. 打开解码器
if (avcodec_open2(pCodecCtxVideo, pCodecVideo, NULL) < 0)
{printf("avcodec_open2 failed\n");return -1;
}
  1. 申请帧内存:存储一帧解码后像素(采样)数据
AVFrame* pFrameVideo = av_frame_alloc(); 
if (!pFrameVideo)
{printf("av_frame_alloc failed\n");return -1;
}

视频解码

  1. 解码一帧压缩数据
// data和len为压缩数据的指针和大小AVPacket packet;
av_init_packet(&packet);
packet.data = (uint8_t*)data;
packet.size = len;int got_picture = 0;
if (avcodec_decode_video2(pCodecCtxVideo, pFrameVideo, &got_picture, &packet) < 0)
{printf("avcodec_decode_video2 failed\n");return -1;
}
  1. 获取帧大小
// 以YUV420为例
int frameSize = avpicture_get_size(AV_PIX_FMT_YUV420P, pFrameVideo->linesize[0], pFrameVideo->height);
  1. 获取上下文,获取用于转码的参数**(初始化一次)**
// pFrameVideo->width:输入帧数据宽
// pFrameVideo->height:输入帧数据高
// pCodecCtxVideo->pix_fmt:帧数据格式
// pFrameVideo->width:输出帧数据宽
// pFrameVideo->height:输出帧数据高
// AV_PIX_FMT_YUV420P:输出帧数据格式,例如YUV420、RGB32等
// SWS_BICUBIC:视频像素数据格式转换算法类型
SwsContext* imgConvertCtx = sws_getContext(pFrameVideo->width, pFrameVideo->height,pCodecCtxVideo->pix_fmt,pFrameVideo->width, pFrameVideo->height, AV_PIX_FMT_YUV420P, SWS_BICUBIC, NULL, NULL, NULL);
  1. 缓冲区分配缓存**(初始化一次)**
int frameSize = avpicture_get_size(AV_PIX_FMT_YUV420P, pFrameVideo->width, pFrameVideo->height);
AVFrame* picture = av_frame_alloc();
uint8_t* pictureBuf = new uint8_t[frameSize];
  1. 初始化缓冲区**(初始化一次)**
avpicture_fill((AVPicture *)m_picture, m_pictureBuf, AV_PIX_FMT_YUV420P, pFrameVideo->width, pFrameVideo->height);
  1. 图片转换**(针对实时流或读取的文件流,循环调用)**
sws_scale(imgConvertCtx, (const uint8_t* const*)pFrameVideo->data, pFrameVideo->linesize, 0, pFrameVideo->height, picture->data, picture->linesize);

解码关闭

if (nullptr != pCodecCtxVideo)
{avcodec_close(pCodecCtxVideo);av_free(pCodecCtxVideo);pCodecCtxVideo = nullptr;
}if (nullptr != pFrameVideo)
{av_frame_free(&pFrameVideo);pFrameVideo = nullptr;
}if (nullptr != picture)
{av_frame_free(&picture);picture = nullptr;
}if (nullptr != pictureBuf)
{delete[] pictureBuf;pictureBuf = nullptr;
}if (nullptr != imgConvertCtx)
{sws_freeContext(imgConvertCtx);imgConvertCtx = nullptr;
}

音频解码

初始化

  1. 音频常用的编解码器id定义
AV_CODEC_ID_PCM_ALAW
AV_CODEC_ID_PCM_MULAW
AV_CODEC_ID_FIRST_AUDIO
AV_CODEC_ID_AAC
  1. 查找解码器:根据编解码id查看解码器
AVCodec* pCodecAudio = avcodec_find_decoder(codecID);
if (!pCodecAudio)
{printf("audio avcodec_find_decoder failed\n");return -1;
}
  1. 申请编码器上下文结构体内存,保存了音频编解码相关信息
AVCodecContext* pCodecCtxAudio = avcodec_alloc_context3(pCodecAudio);
if (!pCodecCtxAudio)
{printf("audio avcodec_alloc_context3 failed\n");return -1;
}
  1. 打开解码器
int audioCodecType = (int)codec;
switch (audioCodecType)
{case CODEC_AUDIO_AAC:break;case CODEC_AUDIO_MP3:break;case CODEC_AUDIO_G711:case CODEC_AUDIO_G711U:pCodecCtxAudio->codec_type = AVMEDIA_TYPE_AUDIO;pCodecCtxAudio->sample_fmt = AV_SAMPLE_FMT_S16;pCodecCtxAudio->sample_rate = 8000;pCodecCtxAudio->channel_layout = AV_CH_LAYOUT_MONO;pCodecCtxAudio->channels = 1;break;case CODEC_AUDIO_G7231:break;case CODEC_AUDIO_G7221:break;default:break;
}pCodecCtxAudio->codec_id = codecID;
int ret = avcodec_open2(pCodecCtxAudio, pCodecAudio, NULL);
if (ret < 0)
{printf("audio avcodec_open2 failed\n");return -1;
}
  1. 申请内存和初始化参数
AVFrame* frameAudio = av_frame_alloc();
if (!frameAudio)
{printf("audio av_frame_alloc failed\n");return -1;
}AVPacket* audioPacket = av_packet_alloc();
if (!audioPacket)
{printf("av_packet_alloc failed\n");return -1;
}
av_init_packet(audioPacket);

音频解码

  1. 解码一帧音频数据
audioPacket->data = (uint8_t*)data;
audioPacket->size = datalen;int ret = avcodec_send_packet(m_pCodecCtxAudio, m_audioPacket);
if (ret < 0) 
{av_packet_unref(audioPacket);printf("audio avcodec_send_packet failed\n");return -1;
}
  1. 接收一帧数据
ret = avcodec_receive_frame(m_pCodecCtxAudio, m_frameAudio);
if (ret < 0)
{return -1;
}
  1. 设置输入和输出音频信息**(执行一次)**
// 分配SwrContext
SwrContext* audioSwrCtx = swr_alloc();
int channelLayout = av_get_default_channel_layout(frameAudio->channels);// audioSwrCtx:重采样申请的内存。如果传NULL,内部会申请一块内存,非NULL可以复用之前的内存
// AV_CH_LAYOUT_MONO:目标声道
// AV_SAMPLE_FMT_S16:目标采样格式
// frameAudio->sample_rate:目标采样率
// channelLayout:原始声道布局
// pCodecCtxAudio->sample_fmt:原始采样格式
// frameAudio->sample_rate:原始采样率
// 设置输入和输出的音频信息
swr_alloc_set_opts(audioSwrCtx, AV_CH_LAYOUT_MONO, AV_SAMPLE_FMT_S16,frameAudio->sample_rate,channelLayout, pCodecCtxAudio->sample_fmt, frameAudio->sample_rate, 0, NULL);// 设置用户参数后初始化上下文
swr_init(audioSwrCtx);
  1. 重采样转换(循环执行)
// audioSwrCtx:音频重采样的上下文
// audioBuffer:输出的指针。传递的输出的数组
// 1024*256:输出的样本数量,不是字节数。单通道的样本数量。
// (const uint8_t**)frameAudio->data:输入的数组,AVFrame解码出来的DATA
// frameAudio->nb_samples:输入的单通道的样本数量。
// 以单声道为例
int len = swr_convert(audioSwrCtx, &audioBuffer, 1024*256,(const uint8_t**)frameAudio->data,frameAudio->nb_samples);// 获取音频大小
av_get_channel_layout_nb_channels(AV_CH_LAYOUT_MONO);
int bufSize = av_samples_get_buffer_size(NULL, av_get_channel_layout_nb_channels(AV_CH_LAYOUT_MONO),frameAudio->nb_samples,AV_SAMPLE_FMT_S16, 0);

解码关闭

if (nullptr != pCodecCtxAudio)
{avcodec_close(pCodecCtxAudio);av_free(pCodecCtxAudio);pCodecCtxAudio = nullptr;
}if (nullptr != frameAudio)
{av_frame_free(&frameAudio);frameAudio = nullptr;
}if (nullptr != audioPacket)
{av_packet_unref(audioPacket);av_packet_free(&audioPacket);audioPacket = nullptr;
}if (nullptr != audioSwrCtx)
{swr_free(&audioSwrCtx);audioSwrCtx = nullptr;
}// 其他资源释放

http://www.ppmy.cn/news/1232141.html

相关文章

MongoDB归并连续号段-(待验证)

实现按照不同条件归并连续号段的方式与具体的数据模型和查询需求有关&#xff0c;以下是一种常见的方式&#xff1a; 假设有一个文档集合&#xff0c;包含如下字段&#xff1a; {"_id": ObjectId("613c3050d5d9b45a0de7c290"),"group": "…

LeetCode47-全排列II-剪枝逻辑

参考链接: &#x1f517;:卡尔的代码随想录:全排列II 这里第一层,used只有一个元素为1,代表只取出了1个元素作为排列,第二层used有两个元素为1,代表取出了2个元素作为排列,因为数组有序,所以重复的元素都是挨着的,因此可以使用如下语句去重. 其中visit[i-1]False的话,就是代表…

香港科技大学广州|机器人与自主系统学域博士招生宣讲会—同济大学专场!!!(暨全额奖学金政策)

在机器人和自主系统领域实现全球卓越—机器人与自主系统学域 硬核科研实验室&#xff0c;浓厚创新产学研氛围&#xff01; 教授亲临现场&#xff0c;面对面答疑解惑助攻申请&#xff01; 一经录取&#xff0c;享全额奖学金1.5万/月&#xff01; &#x1f559;时间&#xff1a;…

git撤销某一次commit提交

一&#xff1a;撤销上一次commit提交&#xff0c;但不删除修改的代码 可以使用使用VSCode 二&#xff1a;使用 git reset --hard命令删除提交时&#xff0c;将会删除该提交及其之后的所有更改&#xff08;相当于你想要回滚到的提交的提交ID&#xff09; git reset --hard 版本…

Qt ListWidget

先创建QListWidgetItem&#xff1a; QListWidgetItem* pListItem1 new QListWidgetItem(QIcon(":/resources/editor.png"),u8"editor");QListWidgetItem* pListItem2 new QListWidgetItem(QIcon(":/resources/env.png"),u8"env");Q…

注意这“一前一后” 覆盖伦敦金价格形态的缺点

在伦敦金交易中&#xff0c;投资者除了应用技术指标和K线信号做交易以外&#xff0c;还会采取价格形态作为入场触发的信号。但在实际交易中&#xff0c;价格形态的灵敏程度比前面说的那两者要差一点&#xff0c;那我们要如何应用好价格形态作为触发信号呢&#xff1f;下面我们就…

ChainLight zkSync Era漏洞揭秘

1. 引言 ChainLight研究人员于2023年9月15日&#xff0c;发现了zkSync Era主网的ZK电路的一个soundness bug&#xff0c;并于2023年9月17日&#xff0c;向Matter Labs团队报告了该问题。Matter Labs团队修复了该问题&#xff0c;并奖励了ChainLight团队5万USDC——为首个zkSync…

Java高级编程-----网络编程

网络通信协议 通过计算机网络可以实现多台计算机连接&#xff0c;但是不同计算机的操作系统和硬件体系结构不同&#xff0c;为了提供通信支持&#xff0c;位于同一个网络中的计算机在进行连接和通信时必须要遵守一定的规则&#xff0c;这就好比在道路中行驶的汽车一定要遵守交…