muxer
:是封裝的意思(像 FLV、MP4 都是既有音頻流也有視頻流的封裝體
) demuxer
:是解封裝的意思(好比把 MP4 解封裝成 YUV 和 PCM 數據)markdown
ffmpeg -c:v h264 -c:a aac -i out_dog.mp4 out_dog_cmd_yuv -f f32le out_dog_cmd.pcm
複製代碼
#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
複製代碼
#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);
}
}
複製代碼