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

术语

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

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

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

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

解码过程

1.导入头文件。
#import 
#import 
#import 
#import 
#include 
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;
}

你可能感兴趣的:(FFmpeg笔记(三)-- 解码过程)