FFmpeg入门(一):解码音视频过程,附Demo
字数统计:918 阅读时长 ≈ 3分钟一、前言
项目需要,接触到了FFmpeg这个神级工具,本文初步介绍了使用FFmpeg解码音视频的过程。不包含FFmpeg的部署配置。
二、正文
0.引言
ffmpeg解码过程中用到了两个很重要的结构体, 这两个结构体比较复杂, 用到的次数也非常多.
AVPacket 保存未解码的数据.
AVFrame 保存解码后的数据.
1.解码流程一览
2.示例(复制粘贴完事儿)
#include <stdio.h>
#ifdef __cplusplus
extern "C"
{
#endif
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#ifdef __cplusplus
};
#endif
int openCodecContext(const AVFormatContext *pFormatCtx, int *pStreamIndex, enum AVMediaType type, AVCodecContext **ppCodecCtx)
{
int streamIdx = -1;
// 获取流下标
for (int i = 0; i < pFormatCtx->nb_streams; i++)
{
if (pFormatCtx->streams[i]->codec->codec_type == type)
{
streamIdx = i;
break;
}
}
if (streamIdx == -1)
{
printf("find video stream failed!\n");
exit(-2);
}
// 寻找解码器
AVCodecContext *pCodecCtx = pFormatCtx->streams[streamIdx]->codec;
AVCodec *pCodec = avcodec_find_decoder(pCodecCtx->codec_id);
if (NULL == pCodec)
{
printf("avcode find decoder failed!\n");
exit(-2);
}
//打开解码器
if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0)
{
printf("avcode open failed!\n");
exit(-2);
}
*ppCodecCtx = pCodecCtx;
*pStreamIndex = streamIdx;
return 0;
}
int main(void)
{
AVFormatContext *pInFormatCtx = NULL;
AVCodecContext *pVideoCodecCtx = NULL;
AVCodecContext *pAudioCodecCtx = NULL;
AVPacket *pPacket = NULL;
AVFrame *pFrame = NULL;
int ret;
/* 支持本地文件和网络url */
const char streamUrl[] = "./test.flv";
/* 1. 注册 */
av_register_all();
pInFormatCtx = avformat_alloc_context();
/* 2. 打开流 */
if(avformat_open_input(&pInFormatCtx, streamUrl, NULL, NULL) != 0)
{
printf("Couldn't open input stream.\n");
return -1;
}
/* 3. 获取流的信息 */
if(avformat_find_stream_info(pInFormatCtx, NULL) < 0)
{
printf("Couldn't find stream information.\n");
return -1;
}
int videoStreamIdx = -1;
int audioStreamIdx = -1;
/* 4. 寻找并打开解码器 */
openCodecContext(pInFormatCtx, &videoStreamIdx, AVMEDIA_TYPE_VIDEO, &pVideoCodecCtx);
openCodecContext(pInFormatCtx, &audioStreamIdx, AVMEDIA_TYPE_AUDIO, &pAudioCodecCtx);
pPacket = av_packet_alloc();
pFrame = av_frame_alloc();
int cnt = 30;
while (cnt--)
{
/* 5. 读流数据, 未解码的数据存放于pPacket */
ret = av_read_frame(pInFormatCtx, pPacket);
if (ret < 0)
{
printf("av_read_frame error\n");
break;
}
/* 6. 解码, 解码后的数据存放于pFrame */
/* 视频解码 */
if (pPacket->stream_index == videoStreamIdx)
{
avcodec_decode_video2(pVideoCodecCtx, pFrame, &ret, pPacket);
if (ret == 0)
{
printf("video decodec error!\n");
continue;
}
printf("* * * * * * video * * * * * * * * *\n");
printf("___height: [%d]\n", pFrame->height);
printf("____width: [%d]\n", pFrame->width);
printf("pict_type: [%d]\n", pFrame->pict_type);
printf("___format: [%d]\n", pFrame->format);
printf("* * * * * * * * * * * * * * * * * * *\n\n");
}
/* 音频解码 */
if (pPacket->stream_index == audioStreamIdx)
{
avcodec_decode_audio4(pAudioCodecCtx, pFrame, &ret, pPacket);
if (ret < 0)
{
printf("audio decodec error!\n");
continue;
}
printf("* * * * * * audio * * * * * * * * * *\n");
printf("____nb_samples: [%d]\n", pFrame->nb_samples);
printf("__samples_rate: [%d]\n", pFrame->sample_rate);
printf("channel_layout: [%lu]\n", pFrame->channel_layout);
printf("________format: [%d]\n", pFrame->format);
printf("* * * * * * * * * * * * * * * * * * *\n\n");
}
av_packet_unref(pPacket);
}
av_frame_free(&pFrame);
av_packet_free(&pPacket);
avcodec_close(pVideoCodecCtx);
avcodec_close(pAudioCodecCtx);
avformat_close_input(&pInFormatCtx);
return 0;
}
3. 函数说明
av_register_all() / 使用ffmpeg几乎都要调用这一个函数, 注册ffmpeg各种编解码器, 复用器等. /
avformat_open_input() / 该函数用于打开本地多媒体文件或者网络流媒体url /
avformat_find_stream_info() / 该函数用于读取一部分音视频数据并且获得一些相关的信息 /
avcodec_find_decoder() / 由codec_id或者解码器名称来寻找对应的解码器 /
avcodec_open2() / 初始化解码器 /
av_read_frame() / 读流数据, 读出来的是压缩数据, 存放于AVPacket /
avcodec_decode_video2() / 视频解码 解码后数据为原始数据, 存放于AVFrame /
avcodec_decode_audio4() / 音频解码 解码后数据为原始数据, 存放于AVFrame /
三、真正的正文
1. 首先要初始化一下,使用如下函数:
av_register_all(); //初始化FFMPEG 调用了这个才能正常适用编码器和解码器
使用这个函数完成编码器和解码器的初始化,只有初始化了编码器和解码器才能正常使用,否则会在打开编解码器的时候失败。
2. 接着需要分配一个AVFormatContext,FFMPEG所有的操作都要通过这个AVFormatContext来进行
AVFormatContext *pFormatCtx = avformat_alloc_context();
3.接着调用打开视频文件
char *file_path = "E:in.mp4";
avformat_open_input(&pFormatCtx, file_path, NULL, NULL);
这里文件名先不要使用中文,否则会打开失败.
4.文件打开成功后就是查找文件中的视频流了:
///循环查找视频中包含的流信息,直到找到视频类型的流
///便将其记录下来 保存到videoStream变量中
///这里我们现在只处理视频流 音频流先不管他
for (i = 0; i < pFormatCtx->nb_streams; i++) {
if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) {
videoStream = i;
}
}
///如果videoStream为-1 说明没有找到视频流
if (videoStream == -1) {
printf("Didn't find a video stream.");
return -1;
}
5.现在根据视频流 打开一个解码器来解码:
///查找解码器
pCodecCtx = pFormatCtx->streams[videoStream]->codec;
pCodec = avcodec_find_decoder(pCodecCtx->codec_id);
if (pCodec == NULL) {
printf("Codec not found.");
return -1;
}
///打开解码器
if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0) {
printf("Could not open codec.");
return -1;
}
可以看出 我们可以直接根据查找到的视频流信息获取到解码器。
而且我们并不知道他实际用的是什么编码器。
这就是为什么一开始我们使用FFMPEG来操作,因为很多东西我们可以不关系
6.现在开始读取视频了:
int y_size = pCodecCtx->width * pCodecCtx->height;
AVPacket *packet = (AVPacket *) malloc(sizeof(AVPacket)); //分配一个packet
av_new_packet(packet, y_size); //分配packet的数据
if (av_read_frame(pFormatCtx, packet) < 0)
{
break; //这里认为视频读取完了
}
可以看出 av_read_frame读取的是一帧视频,并存入一个AVPacket的结构中。
7.前面我们说过 视频里面的数据是经过编码压缩的,因此这里我们需要将其解码:
if (packet->stream_index == videoStream)
{
ret = avcodec_decode_video2(pCodecCtx, pFrame, &got_picture,packet);
if (ret < 0) {
printf("decode error.");
return -1;
}
}
8.基本上所有解码器解码之后得到的图像数据都是YUV420的格式,而这里我们需要将其保存成图片文件,因此需要将得到的YUV420数据转换成RGB格式,转换格式也是直接使用FFMPEG来完成:
if (got_picture) {
sws_scale(img_convert_ctx,
(uint8_t const * const *) pFrame->data,
pFrame->linesize, 0, pCodecCtx->height, pFrameRGB->data,
pFrameRGB->linesize);
}
至于YUV420和RGB图像格式的具体内容,这里不用去了解。这里只需要知道有这么个东西就行了,对我们使用FFMPEG转换没有影响。
9.得到RGB数据之后就是直接写入文件(或者直接显示)
SaveFrame(pFrameRGB, pCodecCtx->width, pCodecCtx->height, index++); //保存图片
if (index > 50) return 0; //这里我们就保存50张图片
至此读取视频解码保存成图片就写好了:
本文由simyng创作,
采用知识共享署名4.0 国际许可协议进行许可,转载前请务必署名
文章最后更新时间为:May 13th , 2020 at 09:51 pm