框架
一个封装文件(mp4)如何播放?大体流程如下:
案例
本案例实现在windows环境下,调用ffmpeg4.4.5动态库实现上述从解封装、视频解码、音频解码的全部过程,案例测试通过。由于ffmpeg接口功能网上资料较多,这里就不在赘述,代码如下:
1、DeCoder.h
#pragma once
extern "C" {
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavutil/imgutils.h>
#include <libswscale/swscale.h>
#include <libswresample/swresample.h>
}
#include <iostream>
#include <fstream>
#include <vector>
#define ADTS_HERDER_LEN 7
const int sampling_frequencies[] = {
96000, //0x0
88200, //0x1
64000, //0x2
48000, //0x3
44100, //0x4
32000, //0x5
24000, //0x6
22050, //0x7
16000, //0x8
12000, //0x9
11025, //0xa
8000 //0xb // 0xc d e f是保留的
};
int test_deMuxer();
int test_deCoderH264();
int test_deCoderAac();
2、DeMuxer.cpp
#include "DeCoder.h"
using namespace std;
int adts_header(uint8_t* const p_adts_header, const int data_length, const int profile, const int samplerate, const int channels)
{
int sampling_frequency_index = 3; //48000
int adtsLen = data_length + ADTS_HERDER_LEN; // ADTS的头部信息总是占7个字节
int frequencies_size = sizeof(sampling_frequencies) / sizeof(sampling_frequencies[0]); //求内存的分配大小
int i = 0;
for (i = 0; i < frequencies_size; i++)
{
if (sampling_frequencies[i] == samplerate)
{
sampling_frequency_index = i;
break;
}
}
if (i > frequencies_size)
{
printf("unsupport samplerate:%d \n", samplerate);
return -1;
}
p_adts_header[0] = 0xff; //syncword:0xfff 高8bits 1111 1111 位数不足则用0补齐
p_adts_header[1] = 0xf0; //syncword:0xfff 低4bits 前面的4bit是属于syncword,后面的才是重新赋值 1111 0000
p_adts_header[1] |= (0 << 3); //MPEG Version:0 for MPEG-4,1 for MPEG-2 1bit |= 按位或后赋值 1111 0000 和 0000 = 1111 0000
p_adts_header[1] |= (0 << 1); //Layer:0 2bits 1111 0000 和 00
p_adts_header[1] |= 1; // protection absent:1 不校验 1bit 1111 00000 和 1 = 1111 0000 0000 0001= 1111 0001
p_adts_header[2] = (profile << 6); //profile:profile 2bits profile :1110 0011 左移6位 1100 0000
p_adts_header[2] |= (sampling_frequency_index & 0x0f) << 2; //sampling frequency index:sampling_frequency_index 4bits
p_adts_header[2] |= (0 << 1); //private bit:0 1bit
p_adts_header[2] |= (channels & 0x04) >> 2; //channel configuration:channels 高1bit
p_adts_header[3] = (channels & 0x03) << 6; //channel configuration:channels 低2bits
p_adts_header[3] |= (0 << 5); //original:0 1bit
p_adts_header[3] |= (0 << 4); //home:0 1bit
p_adts_header[3] |= (0 << 3); //copyright id bit:0 1bit
p_adts_header[3] |= (0 << 2); //copyright id start:0 1bit
p_adts_header[3] |= ((adtsLen & 0x1800) >> 11); //frame length:value 高2bits
p_adts_header[4] = (uint8_t)((adtsLen & 0x7f8) >> 3); //frame length:value 中间8bits
p_adts_header[5] = (uint8_t)((adtsLen & 0x7) << 5); //frame length:value 低3bits
p_adts_header[5] |= 0x1f; //buffer fullness:0x7ff 高5bits
p_adts_header[6] = 0xfc; //buffer fullness:0x7ff 低6bits
return 0;
}
void save_packet_to_file(const AVPacket* pkt, FILE* file) {
fwrite(pkt->data, 1, pkt->size, file);
}
int test_deMuxer() {
const char* input_filename = "../test_video/d2.mp4";
const char* video_output_filename = "../test_video/output_video.h264"; // 或者其他格式,取决于视频编码
const char* audio_output_filename = "../test_video/output_audio.aac"; // 或者其他格式,取决于音频编码
AVFormatContext* fmt_ctx = nullptr;
if (avformat_open_input(&fmt_ctx, input_filename, nullptr, nullptr) != 0) {
std::cerr << "Could not open input file." << std::endl;
return -1;
}
if (avformat_find_stream_info(fmt_ctx, nullptr) < 0) {
std::cerr << "Could not find stream information." << std::endl;
avformat_close_input(&fmt_ctx);
return -1;
}
AVCodecParameters* video_codecpar = nullptr;
AVCodecParameters* audio_codecpar = nullptr;
int video_stream_index = -1;
int audio_stream_index = -1;
// 查找视频流和音频流
for (unsigned int i = 0; i < fmt_ctx->nb_streams; i++) {
AVCodecParameters* codecpar = fmt_ctx->streams[i]->codecpar;
AVCodec* codec = avcodec_find_decoder(codecpar->codec_id);
if (codec->type == AVMEDIA_TYPE_VIDEO) {
video_codecpar = codecpar;
video_stream_index = i;
}
else if (codec->type == AVMEDIA_TYPE_AUDIO) {
audio_codecpar = codecpar;
audio_stream_index = i;
}
}
// std::cout << "---" << video_stream_index << ", " << audio_stream_index << std::endl;
if (video_stream_index == -1) {
std::cerr << "Could not find a video stream." << std::endl;
avformat_close_input(&fmt_ctx);
return -1;
}
if (audio_stream_index == -1) {
std::cerr << "Could not find an audio stream." << std::endl;
// 可以选择继续处理视频流,或者退出程序
// avformat_close_input(&fmt_ctx);
// return -1;
}
// 打开输出文件
FILE* video_file, *audio_file;
errno_t err = fopen_s(&video_file, video_output_filename, "wb");
if (err != 0) {
std::cerr << "Could not open video output file." << std::endl;
avformat_close_input(&fmt_ctx);
return -1;
}
err = fopen_s(&audio_file, audio_output_filename, "wb");
if (err != 0 && audio_stream_index != -1) {
std::cerr << "Could not open audio output file." << std::endl;
fclose(video_file);
avformat_close_input(&fmt_ctx);
return -1;
}
const AVBitStreamFilter* bsfilter = av_bsf_get_by_name("h264_mp4toannexb");
if (!bsfilter)
{
std::cerr << "av_bsf_get_by_name." << std::endl;
fclose(video_file);
fclose(audio_file);
avformat_close_input(&fmt_ctx);
return -1;
}
AVBSFContext* bsf_ctx = NULL;
int ret = av_bsf_alloc(bsfilter, &bsf_ctx);
if (ret < 0)
{
std::cerr << "av_bsf_alloc." << std::endl;
fclose(video_file);
fclose(audio_file);
avformat_close_input(&fmt_ctx);
return -1;
}
ret = avcodec_parameters_copy(bsf_ctx->par_in, fmt_ctx->streams[video_stream_index]->codecpar); //拷贝属性
if (ret < 0)
{
std::cerr << "avcodec_parameters_copy." << std::endl;
fclose(video_file);
fclose(audio_file);
avformat_close_input(&fmt_ctx);
return -1;
}
ret = av_bsf_init(bsf_ctx);
if (ret < 0)
{
std::cerr << "av_bsf_init." << std::endl;
fclose(video_file);
fclose(audio_file);
avformat_close_input(&fmt_ctx);
return -1;
}
AVPacket pkt;
while (av_read_frame(fmt_ctx, &pkt) >= 0) {
if (pkt.stream_index == video_stream_index) {
//处理视频
ret = av_bsf_send_packet(bsf_ctx, &pkt);
if (ret < 0)
{
std::cerr << "av_bsf_send_packet." << std::endl;
continue;
}
ret = av_bsf_receive_packet(bsf_ctx, &pkt);
if (ret != 0) {
std::cerr << "av_bsf_receive_packet." << std::endl;
break;
}
save_packet_to_file(&pkt, video_file);
}
else if (pkt.stream_index == audio_stream_index) {
// 注意:对于AAC音频,MP4容器中的原始数据可能不包含ADTS头。
// 如果需要ADTS头,你需要在保存之前手动添加。
//处理音频
uint8_t adts_header_buf[7] = { 0 };
//添加adts头部信息
adts_header(adts_header_buf, pkt.size,
fmt_ctx->streams[audio_stream_index]->codecpar->profile,
fmt_ctx->streams[audio_stream_index]->codecpar->sample_rate,
fmt_ctx->streams[audio_stream_index]->codecpar->channels);
//写adts header
fwrite(adts_header_buf, 1, 7, audio_file);
save_packet_to_file(&pkt, audio_file);
}
av_packet_unref(&pkt);
}
fclose(video_file);
if (audio_file) {
fclose(audio_file);
}
avformat_close_input(&fmt_ctx);
return 0;
}
3、DeCoder_H264.cpp
#include "DeCoder.h"
int test_deCoderH264() {
const char* input_filename = "../test_video/output_video.h264";
const char* output_yuv = "../test_video/output_video.yuv";
AVFormatContext* fmt_ctx = nullptr;
if (avformat_open_input(&fmt_ctx, input_filename, nullptr, nullptr) != 0) {
std::cerr << "Could not open input file." << std::endl;
return -1;
}
if (avformat_find_stream_info(fmt_ctx, nullptr) < 0) {
std::cerr << "Could not find stream information." << std::endl;
avformat_close_input(&fmt_ctx);
return -1;
}
AVCodecParameters* codecpar = nullptr;
AVCodec* codec = nullptr;
AVPacket* pkt = av_packet_alloc();
AVFrame* frameYUV = av_frame_alloc();
SwsContext* sws_ctx = nullptr;
int video_stream_index = -1;
for (unsigned int i = 0; i < fmt_ctx->nb_streams; i++) {
if (fmt_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
video_stream_index = i;
codecpar = fmt_ctx->streams[i]->codecpar;
// 查找对应解码器
codec = avcodec_find_decoder(codecpar->codec_id);
break;
}
}
std::cout << "-------" << fmt_ctx->nb_streams << std::endl;
if (video_stream_index == -1) {
std::cerr << "Could not find a video stream." << std::endl;
av_packet_free(&pkt);
av_frame_free(&frameYUV);
avformat_close_input(&fmt_ctx);
return -1;
}
// 创建解码器上下文
AVCodecContext* codec_ctx = avcodec_alloc_context3(codec);
if (avcodec_parameters_to_context(codec_ctx, codecpar) < 0) {
std::cerr << "Could not copy codec parameters to context." << std::endl;
av_packet_free(&pkt);
av_frame_free(&frameYUV);
avcodec_free_context(&codec_ctx);
avformat_close_input(&fmt_ctx);
return -1;
}
std::cout << "-------" << codec_ctx->width << ", " << codec_ctx->height << std::endl;
// 打开解码器
if (avcodec_open2(codec_ctx, codec, nullptr) < 0) {
std::cerr << "Could not open codec." << std::endl;
av_packet_free(&pkt);
av_frame_free(&frameYUV);
avcodec_free_context(&codec_ctx);
avformat_close_input(&fmt_ctx);
return -1;
}
// 32位字节对齐的方式计算缓冲区大小
int num_bytes = av_image_get_buffer_size(AV_PIX_FMT_YUV420P, codec_ctx->width, codec_ctx->height, 32);
uint8_t* buffer = (uint8_t*)av_malloc(num_bytes * sizeof(uint8_t));
av_image_fill_arrays(frameYUV->data, frameYUV->linesize, buffer, AV_PIX_FMT_YUV420P, codec_ctx->width, codec_ctx->height, 1);
FILE* yuvFile, *yuvFileY;
errno_t err = fopen_s(&yuvFile, output_yuv, "wb");
if (err != 0) {
std::cerr << "Could not open output_filename file." << std::endl;
av_packet_free(&pkt);
av_frame_free(&frameYUV);
avcodec_free_context(&codec_ctx);
avformat_close_input(&fmt_ctx);
return -1;
}
while (av_read_frame(fmt_ctx, pkt) >= 0) {
if (pkt->stream_index == video_stream_index) {
if (avcodec_send_packet(codec_ctx, pkt) == 0) {
while (avcodec_receive_frame(codec_ctx, frameYUV) == 0) {
// YUV存入另一个文件
fwrite(frameYUV->data[0], 1, codec_ctx->width * codec_ctx->height, yuvFile);
fwrite(frameYUV->data[1], 1, codec_ctx->width * codec_ctx->height/4, yuvFile);
fwrite(frameYUV->data[2], 1, codec_ctx->width * codec_ctx->height/4, yuvFile);
}
}
}
av_packet_unref(pkt);
}
fclose(yuvFile);
av_frame_free(&frameYUV);
avcodec_free_context(&codec_ctx);
av_packet_free(&pkt);
avformat_close_input(&fmt_ctx);
av_free(buffer);
return 0;
}
4、DeCoder_AAC.cpp
#include "DeCoder.h"
int test_deCoderAac(){
const char* input_file = "../test_video/output_audio.aac";
const char* output_file = "../test_video/output_audio.pcm";
// 打开输入文件
AVFormatContext* fmt_ctx = nullptr;
if (avformat_open_input(&fmt_ctx, input_file, nullptr, nullptr) < 0) {
std::cerr << "Could not open input file\n";
return 1;
}
// 查找流信息
if (avformat_find_stream_info(fmt_ctx, nullptr) < 0) {
std::cerr << "Could not find stream information\n";
return 1;
}
// 查找音频流
int audio_stream_index = av_find_best_stream(fmt_ctx, AVMEDIA_TYPE_AUDIO, -1, -1, nullptr, 0);
if (audio_stream_index < 0) {
std::cerr << "Could not find audio stream\n";
return 1;
}
// 获取解码器参数
AVCodecParameters* codec_params = fmt_ctx->streams[audio_stream_index]->codecpar;
// 查找解码器
const AVCodec* codec = avcodec_find_decoder(codec_params->codec_id);
if (!codec) {
std::cerr << "Unsupported codec\n";
return 1;
}
// 创建解码器上下文
AVCodecContext* codec_ctx = avcodec_alloc_context3(codec);
if (avcodec_parameters_to_context(codec_ctx, codec_params) < 0) {
std::cerr << "Could not initialize codec context\n";
return 1;
}
// 打开解码器
if (avcodec_open2(codec_ctx, codec, nullptr) < 0) {
std::cerr << "Could not open codec\n";
return 1;
}
// 准备重采样器(AAC解码后通常为FLTP格式,转换为S16格式)
SwrContext* swr = swr_alloc_set_opts(nullptr,
AV_CH_LAYOUT_STEREO, // 输出声道布局
AV_SAMPLE_FMT_S16, // 输出格式
codec_ctx->sample_rate, // 输出采样率
codec_ctx->channel_layout ?
codec_ctx->channel_layout :
av_get_default_channel_layout(codec_ctx->channels), // 输入声道布局
codec_ctx->sample_fmt, // 输入格式
codec_ctx->sample_rate, // 输入采样率
0, nullptr);
// 检查重采样器初始化
if (!swr || swr_init(swr) < 0) {
std::cerr << "Failed to initialize resampler\n";
return 1;
}
// 准备数据包和帧
AVPacket* pkt = av_packet_alloc();
AVFrame* frame = av_frame_alloc();
// 打开输出文件
std::ofstream outfile(output_file, std::ios::binary);
if (!outfile.is_open()) {
std::cerr << "Could not open output file\n";
return 1;
}
// 解码循环
while (av_read_frame(fmt_ctx, pkt) >= 0) {
if (pkt->stream_index == audio_stream_index) {
// 发送数据包到解码器
if (avcodec_send_packet(codec_ctx, pkt) < 0) {
std::cerr << "Error sending packet\n";
continue;
}
// 接收解码后的帧
while (avcodec_receive_frame(codec_ctx, frame) >= 0) {
// 检查帧有效性
if (frame->nb_samples == 0 || !frame->data) {
continue;
}
// 分配输出缓冲区
uint8_t* output_buffer = nullptr;
int out_linesize;
int ret = av_samples_alloc(&output_buffer, &out_linesize,
2, // 输出声道数(立体声)
frame->nb_samples,
AV_SAMPLE_FMT_S16, 0);
if (ret < 0) {
std::cerr << "Failed to allocate samples\n";
continue;
}
// 重采样
int sample_count = swr_convert(swr,
&output_buffer, frame->nb_samples,
(const uint8_t**)frame->data, frame->nb_samples);
// 写入PCM数据
if (sample_count > 0) {
int data_size = av_samples_get_buffer_size(nullptr, 2,
sample_count,
AV_SAMPLE_FMT_S16, 1);
outfile.write(reinterpret_cast<char*>(output_buffer), data_size);
}
av_freep(&output_buffer);
}
}
av_packet_unref(pkt);
}
// 清理资源
av_packet_free(&pkt);
av_frame_free(&frame);
swr_free(&swr);
avcodec_free_context(&codec_ctx);
avformat_close_input(&fmt_ctx);
outfile.close();
std::cout << "Decoding completed successfully\n";
return 0;
}
5、main.cpp
#include "DeCoder.h"
int main() {
av_log_set_level(AV_LOG_DEBUG);
test_deMuxer();
test_deCoderH264();
test_deCoderAac();
return 0;
}
上述代码已验证通过,笔者用的运行环境是VS2019,所用的ffmpeg4.4.5库是自己编译的x64库。项目路径下载地址:https://download.csdn.net/download/ddazz0621/90519707;运行成功最终生成如下文件:
需要注意:想验证最后生成的pcm播放,调用ffplay执行如下命令:
ffplay -f s16le -ar 8000 -ac 2 output_audio.pcm