整个解码 解复用解码流程如下
本文仅针对同步控制之前的流程 本文仅针对同步控制之前的流程 本文仅针对同步控制之前的流程
解复用与解协议都是同一套流程 先对一个封装格式的文件进行解复用之后才能进行编解码 所以下面我们先从解复用解协议开始
解复用解协议的大致流程如下 详细解释请看我的代码 注释非常详细
代码如下
//因为是C库 所以再引入头文件时必须要使用 extern "C"的方式引入
extern "C"
{
#include <libavcodec/avcodec.h>
#include <libavutil/frame.h>
#include <libavutil/mem.h>
#include <libavcodec/avcodec.h>
#include <libavutil/imgutils.h>
#include <libavutil/samplefmt.h>
#include <libavutil/timestamp.h>
#include <libavformat/avformat.h>
}
#include <iostream>
#include <string>
using namespace std;
int main()
{
string url;//这里是你需要解复用解协议的本地文件或者地址
AVFormatContext* fmt_ctx;//解复用器上下文 注意ffmpeg大多数结构体都是需要分配空间的
//这里不用给解复用器上下文分配空间 因为avformat_open_input会自动分配空间
//但是我这里还是分配了 为了给你们做个示范
fmt_ctx=avformat_alloc_context();
//第一个参数是复用器上下文 第二个是地址 第三个和第四个是你希望的输入输出复用格式 初学者不用管他
avformat_open_input(&fmt_ctx, url.c_str(), nullptr, nullptr);
//根据你打开的这个文件来填充该上下文中流的信息 一个复用文件中可能包含多个视频流和音频流
avformat_find_stream_info(fmt_ctx, nullptr);
//这个函数可以根据你想要视频流还是音频流来返回这个流在解复用上下文中的索引
//第二个参数是你希望找到视频流还是音频流 至于后三个参数初学者不用管 ffmpeg会帮你做好
//这个索引非常关键 在后文解出packet时我们还会用到
int video_index=av_find_best_stream(fmt_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, nullptr,0);
int audio_index= av_find_best_stream(fmt_ctx, AVMEDIA_TYPE_AUDIO, -1, -1, nullptr, 0);
//给Packet分配内存空间 没啥好说的
AVPacket* pkt;
pkt = av_packet_alloc();
//从打开的文件中读取一个Packet 注意 此时既有可能读出音频的packet 也有可能是视频的packet
//必须通过上文我们拿到的流的索引来判断 至此 已经完成了解复用解协议的过程
while (1)
{
//拿出解复用解协议之后的包
av_read_frame(fmt_ctx, pkt);
}
}
接下来 我们继续完成解码的流程 注释非常详细 注意 会对上文的代码进行修改 而不是单纯的添加代码 但是大体改动不大 主要是在av_find_best_stream之后对编解码器进行打开
流程图如下
代码如下:
//因为是C库 所以再引入头文件时必须要使用 extern "C"的方式引入
extern "C"
{
#include <libavcodec/avcodec.h>
#include <libavutil/frame.h>
#include <libavutil/mem.h>
#include <libavcodec/avcodec.h>
#include <libavutil/imgutils.h>
#include <libavutil/samplefmt.h>
#include <libavutil/timestamp.h>
#include <libavformat/avformat.h>
}
#include <iostream>
#include <string>
using namespace std;
int main()
{
string url = "C:\\Users\\86189\\Desktop\\FFmpeg环境\\test.mp4";//这里是你需要解复用解协议的本地文件或者地址
AVFormatContext* fmt_ctx;//解复用器上下文 注意ffmpeg大多数结构体都是需要分配空间的
//这里不用给解复用器上下文分配空间 因为avformat_open_input会自动分配空间
//但是我这里还是分配了 为了给你们做个示范
fmt_ctx = avformat_alloc_context();
//第一个参数是复用器上下文 第二个是地址 第三个和第四个是你希望的输入输出复用格式 初学者不用管他
avformat_open_input(&fmt_ctx, url.c_str(), nullptr, nullptr);
//根据你打开的这个文件来填充该上下文中流的信息 一个复用文件中可能包含多个视频流和音频流
avformat_find_stream_info(fmt_ctx, nullptr);
//这个函数可以根据你想要视频流还是音频流来返回这个流在解复用上下文中的索引
//第二个参数是你希望找到视频流还是音频流 至于后三个参数初学者不用管 ffmpeg会帮你做好
//这个索引非常关键 在后文解出packet时我们还会用到
int video_index = av_find_best_stream(fmt_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, nullptr, 0);
int audio_index = av_find_best_stream(fmt_ctx, AVMEDIA_TYPE_AUDIO, -1, -1, nullptr, 0);
//根据索引分别找到真正的视频流和音频流
//注意 这里流中已经包含了我们需要的解码器信息 所以下文直接通过流中的信息来找到编解码器并打开
AVStream* video_stream = fmt_ctx->streams[video_index];
AVStream* audio_stream = fmt_ctx->streams[audio_index];
//音视频编解码器上下文
AVCodecContext* video_codec_ctx{};
AVCodec* video_dec;
AVCodecContext* audio_codec_ctx{};
AVCodec* audio_dec;
//根据流中带的视频编解码器信息去找到对于的视频编解码器
video_dec = avcodec_find_decoder(video_stream->codecpar->codec_id);
//给视频编解码器上下文分配内存空间
video_codec_ctx = avcodec_alloc_context3(video_dec);
//把码流视频编解码器信息填入到编解码器上下文中
avcodec_parameters_to_context(video_codec_ctx, video_stream->codecpar);
//打开视频编解码器并把编解码器信息关联到解码器上下文中
avcodec_open2(video_codec_ctx, video_dec, nullptr);
//根据流中带的音频编解码器信息去找到对于的音频编解码器
audio_dec = avcodec_find_decoder(audio_stream->codecpar->codec_id);
//给音频编解码器上下文分配内存空间
audio_codec_ctx = avcodec_alloc_context3(audio_dec);
//把音频编解码器信息填入到编解码器上下文中
avcodec_parameters_to_context(audio_codec_ctx, audio_stream->codecpar);
//打开音频编解码器
avcodec_open2(audio_codec_ctx, audio_dec, nullptr);
//给Packet分配内存空间 并且初始化 没啥好说的
AVPacket* pkt;
pkt = av_packet_alloc();
av_init_packet(pkt);
//给frame分配内存空间 没啥好说的
AVFrame* frame;
frame = av_frame_alloc();
while (1)//这个循环
{
//拿到一个包 注意这里虽然是 read_frame 但其实是去拿packet而不是frame
//从打开的文件中读取一个Packet 注意 此时既有可能读出音频的packet 也有可能是视频的packet
//必须通过上文我们拿到的流的索引来判断
av_read_frame(fmt_ctx, pkt);
//如果读到了文件尾 或者是拉流超时了 就应该退出循环 但是我这里没做
if (pkt->stream_index == video_index)//如果是视频包
{
//把这个包发送到解码器去解码
avcodec_send_packet(video_codec_ctx, pkt);
//去解码器拿到解码完成的帧
avcodec_receive_frame(video_codec_ctx, frame);
//释放资源
av_frame_unref(frame);
av_packet_unref(pkt);
}
else//如果是音频包
{
//把这个包发送到解码器去解码
avcodec_send_packet(audio_codec_ctx, pkt);
//去解码器拿到解码完成的帧
avcodec_receive_frame(audio_codec_ctx, frame);
//释放资源
av_frame_unref(frame);
av_packet_unref(pkt);
}
//至此 我们已经完成了解复用和解码
}
//释放资源
avcodec_free_context(&audio_codec_ctx);
avcodec_free_context(&video_codec_ctx);
avformat_free_context(fmt_ctx);
}