FFmpeg笔记(三)-- 解码过程

术语

容器/文件(Conainer/File):即特定格式的多媒体文件,比如 MP4、flv、mov等。

媒体流(Stream):表示时间轴上的一段连续数据,如一段声音数据、一段视频数据或一段字幕数据,可以是压缩的,也可以是非压缩的,压缩的数据需要关联特定的编解码器。

数据帧/数据包(Frame/Packet):通常,一个媒体流是由大量的数据帧组成的,对于压缩数据,帧对应着编解码器的最小处理单元,分属于不同媒体流的数据帧交错存储于容器之中。

编解码器:编解码器是以帧(针对视频,音频没有帧的概念,可以理解为一段音频数据)为单位实现压缩数据和原始数据之间的相互转换的。

解码过程

1.导入头文件。
#import <libavformat/avformat.h>
#import <libswscale/swscale.h>
#import <libswresample/swresample.h>
#import <libavutil/pixdesc.h>
#include <libavutil/imgutils.h>
2.注册协议、格式、编解码器。

调用FFmpeg的注册协议、格式与编解码器的方法,确保所有的格式与编解码器都被注册到了FFmpeg框架中。

av_register_all(); //新库已经废弃了这个函数,不再需要注册
3.创建上下文,打开媒体文件源。

注册了格式以及编解码器之后,接下来就应该打开对应的媒体文件了,当然该文件既可能是本地磁盘的文件,也可能是网络媒体资源的一个链接,如果是网络链接,则会涉及不同的协议,比如RTMP、HTTP等协议的视频源。

AVFormatContext *formatCtx = avformat_alloc_context();
avformat_open_input(&formatCtx, path, NULL, NULL);
avformat_find_stream_info(formatCtx, NULL);
4.寻找各个流,并且打开对应的解码器。

这一步我们要寻找出各个流,然后找到流中对应的解码器,并且打开它。
寻找音视频流:

for(int i = 0; i < formatCtx->nb_streams; i++) { 
    AVStream* stream = formatCtx->streams[i]; 
    if(AVMEDIA_TYPE_VIDEO == stream->codec->codec_type) {
       // 视频流
       videoStreamIndex = i;
    } else if(AVMEDIA_TYPE_AUDIO == stream->codec->codec_type ) {
       // 音频流
       audioStreamIndex = i;
    }
}

打开音频流解码器:

AVCodecContext * audioCodecCtx = avcodec_alloc_context3(NULL);
avcodec_parameters_to_context(audioCodecCtx, audioStream->codecpar);
AVCodec *codec = avcodec_find_decoder(audioCodecCtx ->codec_id);
if(!codec) {
    // 找不到对应的音频解码器 
}
int openCodecErrCode = 0;
if ((openCodecErrCode = avcodec_open2(codecCtx, codec, NULL)) < 0) {
     // 打开音频解码器失败
}

打开视频流解码器:


AVCodecContext *videoCodecCtx = avcodec_alloc_context3(NULL);
avcodec_parameters_to_context(videoCodecCtx, videoStream->codecpar);
AVCodec *codec = avcodec_find_decoder(videoCodecCtx->codec_id); 
if(!codec) {
    // 找不到对应的视频解码器 
}
int openCodecErrCode = 0;
if ((openCodecErrCode = avcodec_open2(codecCtx, codec, NULL)) < 0) {
    // 打开视频解码器失败
 }
5.初始化解码后数据的结构体。

分配出解码之后的数据所存放的内存空间,以及进行格式转换需要用到的对象。
构建音频的格式转换对象以及音频解码后数据存放的对象:

SwrContext *swrContext = NULL;
if(audioCodecCtx->sample_fmt != AV_SAMPLE_FMT_S16) {
     // 如果不是我们需要的数据格式,则重新采样
     swrContext = swr_alloc_set_opts(NULL,outputChannel, AV_SAMPLE_FMT_S16, outSampleRate,
        in_ch_layout, in_sample_fmt, in_sample_rate, 0, NULL);

     if(!swrContext || swr_init(swrContext)) {
         if(swrContext) {
            swr_free(&swrContext);
        }
     }
     audioFrame = av_frame_alloc();
}

构建视频的格式转换对象以及视频解码后数据存放的对象:

AVFrame *videoFrame = avcodec_alloc_frame();
//创建缓冲区
av_image_get_buffer_size(AV_PIX_FMT_YUV420P,
                         videoCodecCtx->width,
                         videoCodecCtx->height,
                         1);
//开辟一块内存空间
uint8_t *out_buffer = (uint8_t *)av_malloc(buffer_size);
//缓冲区数据填充
av_image_fill_arrays(avframe_yuv420p->data,
                     avframe_yuv420p->linesize,
                     out_buffer,
                     AV_PIX_FMT_YUV420P,
                     videoCodecCtx->width,
                     videoCodecCtx->height,
                     1);
//初始化视频数据存储对象
swsContext = sws_getContext(swsContext, videoCodecCtx->width,videoCodecCtx->height, videoCodecCtx->pix_fmt, videoCodecCtx->width, videoCodecCtx->height, PIX_FMT_YUV420P, SWS_FAST_BILINEAR,NULL, NULL, NULL); 

6.读取流内容并且解码。

读取一部分流中的数据(压缩数据), 然后将压缩数据作为解码器的输入,解码器将其解码为原始数据(裸数 据),之后就可以将原始数据写入文件了:

AVPacket packet;
while(true) {
  if(av_read_frame(formatContext, &packet)) { // End Of File
    break;
  }
  int packetStreamIndex = packet.stream_index; 
  if(packetStreamIndex == videoStreamIndex) {
      int send_result = avcodec_send_packet(videoCodecCtx, packet);
      avcodec_receive_frame(videoCodecCtx, videoFrame);
      if(send_result == 0) {
          //处理视频数据,见【7.】
      }
      if(gotFrame) {
          self->handleVideoFrame(); 
      }
  } else if(packetStreamIndex == audioStreamIndex) {
      int send = avcodec_send_packet(audioCodecCtx, packet);
      avcodec_receive_frame(audioCodecCtx, audioFrame);
      if(send == 0) {
          //处理音频数据,见【7.】
      }
  }
}
7.处理解码后的裸数据。

解码之后会得到裸数据,音频就是PCM数据,视频就是YUV数据。下面将其处理成我们所需要的格式并且进行写文件。 音频裸数据的处理:

void* audioData;
int numFrames;
if(swrContext) {
   int bufSize = av_samples_get_buffer_size(NULL, channels,(int)(audioFrame->nb_samples * channels),AV_SAMPLE_FMT_S16, 1);
   if (!_swrBuffer || _swrBufferSize < bufSize) {
      swrBufferSize = bufSize;
      swrBuffer = realloc(_swrBuffer, _swrBufferSize); 
   }
   Byte *outbuf[2] = { _swrBuffer, 0 }; 
   numFrames = swr_convert(_swrContext, outbuf,(int)(audioFrame->nb_samples * channels), (const uint8_t **)_audioFrame->data, audioFrame->nb_samples);
   audioData = swrBuffer;
 } else {
   audioData = audioFrame->data[0]; 
   numFrames = audioFrame->nb_samples;
}

接收到音频裸数据之后,就可以直接写文件了,比如写到文件 audio.pcm中。
视频裸数据的处理:

uint8_t* luma;
uint8_t* chromaB;
uint8_t* chromaR;
if(videoCodecCtx->pix_fmt == AV_PIX_FMT_YUV420P ||
   videoCodecCtx->pix_fmt == AV_PIX_FMT_YUVJ420P) { 

   luma = copyFrameData(videoFrame->data[0],videoFrame->linesize[0], 
                         videoCodecCtx->width, videoCodecCtx->height);
   chromaB = copyFrameData(videoFrame->data[1], videoFrame->linesize[1],         
                            videoCodecCtx->width / 2, videoCodecCtx->height / 2);
   chromaR = copyFrameData(videoFrame->data[2], videoFrame->linesize[2], 
                            videoCodecCtx->width / 2, videoCodecCtx->height / 2);
} else { 
   sws_scale(_swsContext,(const uint8_t **)videoFrame->data, videoFrame->linesize,0,
            videoCodecCtx->height,picture.data,picture.linesize);
   luma = copyFrameData(picture.data[0],picture.linesize[0], 
                       videoCodecCtx->width, videoCodecCtx->height);
   chromaB = copyFrameData(picture.data[1], picture.linesize[1], 
                          videoCodecCtx->width / 2, videoCodecCtx->height / 2);
   chromaR = copyFrameData(picture.data[2], picture.linesize[2], 
                          videoCodecCtx->width / 2, videoCodecCtx->height / 2);
}

接收到YUV数据之后也可以直接写入文件了,比如写到文件 video.yuv中。

8.关闭所有资源。

解码结束或中途退出需要将用到的FFmpeg框架中的资源,包括 FFmpeg框架对外的连接资源等全都释放掉。
关闭音频资源:

if (swrBuffer) {
   free(swrBuffer);
   swrBuffer = NULL;
   swrBufferSize = 0;
}
if (swrContext) { 
   swr_free(&swrContext); 
   swrContext = NULL;
}
if (audioFrame) {
   av_free(audioFrame);
   audioFrame = NULL;
}
if (audioCodecCtx) { 
   avcodec_close(audioCodecCtx);
   audioCodecCtx = NULL;
}

关闭视频资源:

if (swsContext) { 
   sws_freeContext(swsContext); 
   swsContext = NULL;
}
if (pictureValid) {
   avpicture_free(&picture);
   pictureValid = false;
}
if (videoFrame) { 
   av_free(videoFrame); 
   videoFrame = NULL;
}
if (videoCodecCtx) {
   avcodec_close(videoCodecCtx);
   videoCodecCtx = NULL;
}

关闭连接资源:

if (formatCtx) { 
  avformat_close_input(&formatCtx); 
  formatCtx = NULL;
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 220,492评论 6 513
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 94,048评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,927评论 0 358
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,293评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,309评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 52,024评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,638评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,546评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,073评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,188评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,321评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,998评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,678评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,186评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,303评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,663评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,330评论 2 358

推荐阅读更多精彩内容