音視頻 day2四、25 解封裝格式

1. muxer 和 demuxer 分別是什麼?

muxer:是封裝的意思(像 FLV、MP4 都是既有音頻流也有視頻流的封裝體demuxer:是解封裝的意思(好比把 MP4 解封裝成 YUV 和 PCM 數據)markdown

2. 如何須要播放一個封裝格式的 MP4文件,主思路是什麼?(必需要能說出四大步驟)

image.png

3. 如何使用命令行,從 MP4 中提取 YUV 和 PCM?(做爲後續代碼提取的參照物)

ffmpeg -c:v h264 -c:a aac -i out_dog.mp4 out_dog_cmd_yuv -f f32le out_dog_cmd.pcm
複製代碼

4. 解封裝關鍵代碼以下

  • ffmpegs.h
#ifndef FFMPEGS_H
#define FFMPEGS_H

#include <QFile>

extern "C" {
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavutil/avutil.h>
}

typedef struct {
 const char *filename;
 int sampleRate;
 AVSampleFormat sampleFmt;
 int chLayout;
} AudioDecodeSpec;

typedef struct {
 const char *filename;
 int width;
 int height;
 AVPixelFormat pixFmt;
 int fps;
} VideoDecodeSpec;

class FFmpegs {
public:
 FFmpegs();

 void demux(const char *inFilename, AudioDecodeSpec &aOut, VideoDecodeSpec &vOut);
 
 ///解封裝上下文
 AVFormatContext *_fmtCtx = nullptr;
 
 ///解碼上下文
 AVCodecContext *_aDecodeCtx = nullptr, *_vDecodeCtx = nullptr;
 
 ///流索引
 int _aStreamIdx = 0, _vStreamIdx = 0;
 // 文件
 QFile _aOutFile, _vOutFile;
 ///函數參數
 AudioDecodeSpec *_aOut = nullptr;
 VideoDecodeSpec *_vOut = nullptr;
 ///存放解碼前的數據
// AVPacket *_pkt = nullptr;
 ///存放解碼後的數據
 AVFrame *_frame = nullptr;
 ///存放一幀解碼圖片的緩衝區
 uint8_t *_imgBuf[4] = {nullptr};
 int _imgLinesizes[4] = {0};
 int _imgSize = 0;
 ///每個音頻樣本幀(包含全部聲道)大小
 int _sampleFrameSize = 0;
 ///每個音頻樣本的大小(單聲道)
 int _sampleSize = 0;
 
 int initVideoInfo();
 int initAudioInfo();
 int initDecoder(AVCodecContext **decodeCtx, int *streamIdx, AVMediaType type);
 int decode(AVCodecContext *decodeCtx, AVPacket *pkt, void (FFmpegs::*func)());
 void writeVideoFrame();
 void writeAudioFrame();
 
 
};

#endif // FFMPEGS_H

複製代碼
  • ffmpegs.m
#include "ffmpegs.h"
#include <QDebug>
#include <QFile>

extern "C" {
#include <libavutil/imgutils.h>
}

#define ERROR_BUF \ char errbuf[1024]; \ av_strerror(ret, errbuf, sizeof (errbuf));

#define END(func) \ if (ret < 0) { \ ERROR_BUF; \ qDebug() << #func << "error" << errbuf; \ goto end; \ }

#define RET(func) \ if (ret < 0) { \ ERROR_BUF; \ qDebug() << #func << "error" << errbuf; \ return ret; \ }


FFmpegs::FFmpegs() {

}

void FFmpegs::demux(const char *inFilename, AudioDecodeSpec &aOut, VideoDecodeSpec &vOut) {
 ///保留參數
 _aOut = &aOut;
 _vOut = &vOut;
 
 AVPacket *pkt = nullptr;
 
 ///返回結果
 int ret = 0;
 
 ///建立解封裝上下文、打開文件
 ret = avformat_open_input(&_fmtCtx, inFilename, nullptr, nullptr);
 END(avformat_open_input);
 
 ///檢索流信息
 ret = avformat_find_stream_info(_fmtCtx, nullptr);
 END(avformat_find_stream_info);
 
 ///打印流信息到控制檯
 av_dump_format(_fmtCtx, 0, inFilename, 0);
 fflush(stderr);
 
 ///初始化音頻信息
 ret = initAudioInfo();
 if (ret < 0) {
     goto end;
 }
 
 ///初始化視頻信息
 ret = initVideoInfo();
 if (ret < 0) {
     goto end;
 }
 
 ///初始化 frame
 _frame = av_frame_alloc();
 if (!_frame) {
     qDebug() << "av_frame_alloc error";
     goto end;
 }
 
 ///初始化 pkt
 pkt = av_packet_alloc();
 pkt->data = nullptr;
 pkt->size = 0;
 
 ///從輸入文件中讀取數據
 while (av_read_frame(_fmtCtx, pkt) == 0) {
     if (pkt->stream_index == _aStreamIdx) { ///讀取到的是音頻數據
         ret = decode(_aDecodeCtx, pkt, &FFmpegs::writeAudioFrame);
     } else if (pkt->stream_index == _vStreamIdx) {///讀取到的是視頻數據
         ret = decode(_vDecodeCtx, pkt, &FFmpegs::writeVideoFrame);
     }
     ///釋放 pkt 內部指針指向的一些額外內存
     av_packet_unref(pkt);
     if (ret < 0) {
         goto end;
     }
 }
 
 ///刷新緩衝區
 decode(_aDecodeCtx, nullptr, &FFmpegs::writeAudioFrame);
 decode(_vDecodeCtx, nullptr, &FFmpegs::writeVideoFrame);
 
end:
 _aOutFile.close();
 _vOutFile.close();
 avcodec_free_context(&_aDecodeCtx);
 avcodec_free_context(&_vDecodeCtx);
 avformat_close_input(&_fmtCtx);
 av_frame_free(&_frame);
 av_packet_free(&pkt);
 av_freep(&_imgBuf[0]);
}

///初始化音頻信息
int FFmpegs::initAudioInfo() {
 ///初始化解碼器
 int ret = initDecoder(&_aDecodeCtx, &_aStreamIdx, AVMEDIA_TYPE_AUDIO);
 RET(initDecoder);
 
 ///打開文件
 _aOutFile.setFileName(_aOut->filename);
 if (!_aOutFile.open(QFile::WriteOnly)) {
     qDebug() << "file open error" << _aOut->filename;
     return -1;
 }
 
 ///保存音頻參數
 _aOut->sampleRate = _aDecodeCtx->sample_rate;
 _aOut->sampleFmt = _aDecodeCtx->sample_fmt;
 _aOut->chLayout = _aDecodeCtx->channel_layout;
 
 // 音頻樣本幀的大小
 _sampleSize = av_get_bytes_per_sample(_aOut->sampleFmt);
 _sampleFrameSize = _sampleSize * _aDecodeCtx->channels;
 
 return 0;
 
}

///初始化視頻信息
int FFmpegs::initVideoInfo() {
 ///初始化解碼器
 int ret = initDecoder(&_vDecodeCtx, &_vStreamIdx, AVMEDIA_TYPE_VIDEO);
 RET(initDecoder);
 
 ///打開文件
 _vOutFile.setFileName(_vOut->filename);
 if (!_vOutFile.open(QFile::WriteOnly)) {
     qDebug() << "file open error" << _vOut->filename;
     return -1;
 }
 
 ///保存視頻參數
 _vOut->width = _vDecodeCtx->width;
 _vOut->height = _vDecodeCtx->height;
 _vOut->pixFmt = _vDecodeCtx->pix_fmt;
 
 ///幀率
 AVRational framerate = av_guess_frame_rate(_fmtCtx,
                                            _fmtCtx->streams[_vStreamIdx],
                                            nullptr);
 
 _vOut->fps = framerate.num / framerate.den;
 
 ///建立用於存放一幀解碼圖片的緩衝區
 ret = av_image_alloc(_imgBuf, _imgLinesizes,
                      _vOut->width, _vOut->height,
                      _vOut->pixFmt, 1);
 RET(av_image_alloc);
 _imgSize = ret;
 
 return 0;
}

///初始化解碼器
int FFmpegs::initDecoder(AVCodecContext **decodeCtx, int *streamIdx, AVMediaType type) {
 ///根據 type 尋找最合適的流信息
 ///返回值是流索引
 int ret = av_find_best_stream(_fmtCtx, type, -1, -1, nullptr, 0);
 RET(av_find_best_stream);
 
 ///檢驗流
 *streamIdx = ret;
 AVStream *stream = _fmtCtx->streams[*streamIdx];
 if (!stream) {
     qDebug() << "stream is empty";
     return -1;
 }
 
 ///爲當前流找到合適的解碼器
 AVCodec *decoder = avcodec_find_decoder(stream->codecpar->codec_id);
 if (!decoder) {
     qDebug() << "decoder not found" << stream->codecpar->codec_id;
     return -1;
 }
 
 /// 初始化解碼上下文
 *decodeCtx = avcodec_alloc_context3(decoder);
 if (!decodeCtx) {
     qDebug() << "avcodec_alloc_context3 error";
     return -1;
 }
 
 ///從流中拷貝參數到解碼上下文中
 ret = avcodec_parameters_to_context(*decodeCtx, stream->codecpar);
 RET(avcodec_parameters_to_context);
 
 ///打開解碼器
 ret = avcodec_open2(*decodeCtx, decoder, nullptr);
 RET(avcodec_open2);
 
 return 0;
}

int FFmpegs::decode(AVCodecContext *decodeCtx, AVPacket *pkt, void (FFmpegs::*func)()) {
 ///發送壓縮數據到解碼器
 int ret = avcodec_send_packet(decodeCtx, pkt);
 RET(avcodec_send_packet);
 
 while (true) {
     ///獲取解碼後的數據
     ret = avcodec_receive_frame(decodeCtx, _frame);
     if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
         return 0;
     }
     RET(avcodec_receive_frame)
     
     ///執行寫入文件的代碼
     (this->*func)();
 }
}

void FFmpegs::writeVideoFrame() {
 ///拷貝 frame 的數據到 _imgBuf 緩衝區
 av_image_copy(_imgBuf, _imgLinesizes,
               (const uint8_t **)_frame->data, _frame->linesize,
               _vOut->pixFmt, _vOut->width, _vOut->height);
 ///將緩衝區的數據寫入到文件
 _vOutFile.write((char *)_imgBuf[0], _imgSize);
}

void FFmpegs::writeAudioFrame() {
 ///libfdk_aac 解碼器,解碼出來的 PCM 格式:s16
 ///aac 解碼器,解碼出來的 PCM 格式:ftlp
 
 ///LLLL RRRR DDDD FFFF
 
 if (av_sample_fmt_is_planar(_aOut->sampleFmt)) { ///planar
     ///外層循環:每個聲道的樣本數
     ///si = sample index
     for (int si = 0; si < _frame->nb_samples; si++) {
         ///內層循環:有多少個聲道
         ///ci = channel index
         for (int ci = 0; ci < _aDecodeCtx->channels; ci++) {
             char *begin = (char*)(_frame->data[ci] + si * _sampleSize);
             _aOutFile.write(begin, _sampleSize);
         }
     }
 } else { ///非 planar
     _aOutFile.write((char *)_frame->data[0], _frame->nb_samples * _sampleFrameSize);
 }
 
}

複製代碼
相關文章
相關標籤/搜索