音视频 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)

相关推荐

  • C 中NULL和nullptr的区别

    在C 中,NULL是一个宏,其实质是0.而nullptr是从C 11开始引入的关键字.在C语言中,NULL的定义为(void *)0,因为C语言可以隐式转换.但在C 中,int *p = (void ...

  • 几种特殊的函数宏封装方式

    来源链接: https://blog.csdn.net/qq_35692077/article/details/102994959 strongerHuang 1 函数宏介绍 函数宏,即包含多条语句的 ...

  • 万兴优转 一款工具轻松转换各种音视频格式

    对于喜欢自己收藏音乐和视频的人来说,相信很多人都遇到过各种音视频文件的问题,比如有些视频格式播放器不支持.有些网站仅支持特定格式的文件.文件上传大小限制等等,为了解决这些问题可能需要安装多款软件,比如 ...

  • 常见视频封装格式(2)

    概述 日常生活中,看到的视频文件的后缀名如 .mp4..avi..rmvb 都是属于视频文件的封装格式.所谓封装格式,就是以怎样的方式将视频轨.音频轨.字幕轨等信息组合在一起.说得通俗点,视频轨相当于 ...

  • 抖音幻术视频项目详解,附制作工具和教程

    问:抖音怎么涨粉最快? 答:内容足够吸引用户. 问:什么样的内容最吸引用户? 答:有用和有趣. 比如抖音上讲医学常识.法律常识,用户看完能得到某种知识,这是有用. 比如抖音上拍搞笑段子.情感段子,用户 ...

  • 十、详解FFplay音视频同步

    [TOC] 开始前的BB 有些没有接触过的童鞋可能还不知道音视频同步是什么意思,大家印象中应该看到过这样的视频,画面中的人物说话和声音出来的不在一起,小时候看有些电视台转播的港片的时候(别想歪 TVB ...

  • 人物(音视频) | 沈子渝:民间音乐家阿炳(江苏无锡话)

    二泉映月 阿炳 - 民间音乐家华彦钧(阿炳)1893-1950纪念专集 阿炳 华彦钧 民间音乐家 阿炳原名华彦钧,是江苏无锡人,民间音乐家,正一派道士.他1893年出生,1950年去世,生活了57年. ...

  • 动图+视频,详解常见手术切口缝合技术

     昨天 来源:骨科在线orthonline 详解常见手术切口缝合技术. 缝合技巧与手法 根据缝针大小和缝合要求选择合适的持针器,于持针器前1/3处夹针体后1/3弧处持针器夹持缝针在缝合过程中缝针不晃动 ...

  • 金山古祠堂群|走进叶氏家庙(附精彩抖音视频)

    距离县城6公里的汝城县土桥镇金山村,有着保存比较完好的古祠堂.去那里休闲玩乐的人,去祠堂走走看看那也是一件两全其美的事. 冬日阳光普照的早晨,带着一种闲情与放松,我以一个土桥老乡的身份走在了这块土地上 ...

  • 一脚跨三省|抖音视频播放量超过11万以上!

    几年前就梦想能去一次的热水氾水山,体验着一种"一脚跨三省"的豪放.在4月11日周六这天下午,梦想终于成真.一路顺风顺水地到达景区,景区守门负责人说还没开园.为了不想让自己的计划落空 ...

  • 方言与景点(音视频)| 黄菲菲:古排水系统—福寿沟(江西于都罗坳客家话)

    你站在来到赣州的外地人,总是会很好奇为何赣州老城区从不会被洪水侵扰,纵使暴雨来袭,居民依旧安安心心地做事,雨水难以打乱他们的生活节奏.后来才知道原来赣州有一个古老的排水系统--福寿沟.直至现今,它依旧 ...