音视频 day24、25 解封装格式
1. muxer 和 demuxer 分别是什么?
muxer
:是封装的意思(像 FLV、MP4 都是既有音频流也有视频流的封装体
) demuxer
:是解封装的意思(比如把 MP4 解封装成 YUV 和 PCM 数据)
2. 如何需要播放一个封装格式的 MP4文件,主思路是什么?(必须要能说出四大步骤)
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);
}
}
赞 (0)