前言
通过前面的实战,我们实现音视频解封装提取、音视频解码、音视频编码、音频重采样等的功能,今天我们就结合之前所做的功能,
来做一个短视频APP中常见的功能:
1、提取多个mp3文件中的音频,重新编码为合并为aac
2、提取mp4中的视频,重新编码合并为h264
3、h264与aac合并成新的mp4文件
因为我们的目的是以实战为主,为了囊括之前所学的一些知识点,在这个实战中我们不仅仅需要实现音视频解封装提取、音视频解码、音视频编码、音频重采样这些功能,
我们还需要结合多线程同步等知识点做好生产者消费者队列缓冲控制。还包含例如类成员函数作为线程执行函数的使用等知识点。
大致框架
这里要说明一个常识就是如果音频如果需要合并的话要保证两个音频的采样率、采样格式以及通道数一致,所以需要重采样,为了测试,笔者把音频都重采样为22050hz。
同时视频也一样,如果视频需要合并也需要保证两个视频的分辨率是一样的,这里笔者统一把尺寸转换为720x1280。
笔者文笔不好,经常一句卧槽走天下,直接看图吧。。。
代码实现
本来笔者想追求简单,希望用一个cpp文件实现的,后面写着写着发现代码量有点多,所以就拆分成了三个cpp文件,下面是代码详情:
AudioHandle.cpp
/**
* 音频处理
* 解码音频,并且重采样为22050,然后编码成aac
*/
#ifndef TARGET_AUDIO_SAMPLE_RATE
// 采样率
#define TARGET_AUDIO_SAMPLE_RATE 22050
#endif
#include <iostream>
#include <vector>
extern "C" {
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavutil/opt.h>
#include <libavutil/channel_layout.h>
#include <libavutil/audio_fifo.h>
#include <libswresample/swresample.h>
};
class AudioHandle {
public:
void handle_audio(std::vector<char *> mp3_paths,
std::function<void(const AVCodecContext *, AVPacket *, bool)> callback) {
// 音频编码器相关
const AVCodec *avCodec = avcodec_find_encoder(AV_CODEC_ID_AAC);
audio_encoder_context = avcodec_alloc_context3(avCodec);
audio_encoder_context->sample_rate = TARGET_AUDIO_SAMPLE_RATE;
// 默认的aac编码器输入的PCM格式为:AV_SAMPLE_FMT_FLTP
audio_encoder_context->sample_fmt = AV_SAMPLE_FMT_FLTP;
audio_encoder_context->channel_layout = AV_CH_LAYOUT_STEREO;
// audio_encoder_context->bit_rate = 128 * 1024;
audio_encoder_context->codec_type = AVMEDIA_TYPE_AUDIO;
audio_encoder_context->channels = av_get_channel_layout_nb_channels(audio_encoder_context->channel_layout);
audio_encoder_context->profile = FF_PROFILE_AAC_LOW;
//ffmpeg默认的aac是不带adts,而fdk_aac默认带adts,这里我们强制不带
audio_encoder_context->flags = AV_CODEC_FLAG_GLOBAL_HEADER;
int ret = avcodec_open2(audio_encoder_context, avCodec, nullptr);
if (ret < 0) {
std::cout << "音频编码器打开失败" << std::endl;
return;
}
// 初始化audiofifo
audiofifo = av_audio_fifo_alloc(audio_encoder_context->sample_fmt, audio_encoder_context->channels,
audio_encoder_context->frame_size);
AVFormatContext *avFormatContext = nullptr;
AVCodecContext *decoder_context = nullptr;
AVPacket *avPacket = av_packet_alloc();
AVFrame *avFrame = av_frame_alloc();
std::vector<AVPacket *> pack_vector = std::vector<AVPacket *>();
while (!mp3_paths.empty()) {
// 先释放旧的
avcodec_free_context(&decoder_context);
avformat_free_context(avFormatContext);
const char *mp3 = mp3_paths.at(0);
mp3_paths.erase(mp3_paths.cbegin());
avFormatContext = avformat_alloc_context();
ret = avformat_open_input(&avFormatContext, mp3, nullptr, nullptr);
if (ret < 0) {
std::cout << "音频文件打开失败" << std::endl;
break;
}
int audio_index = av_find_best_stream(avFormatContext, AVMEDIA_TYPE_AUDIO, -1, -1, nullptr, 0);
if (audio_index < 0) {
for (int i = 0; i < avFormatContext->nb_streams; ++i) {
if (AVMEDIA_TYPE_AUDIO == avFormatContext->streams[i]->codecpar->codec_type) {
audio_index = i;
std::cout << "找到音频流,audio_index:" << audio_index << std::endl;
break;
}
}
if (audio_index < 0) {
std::cout << "没有找到音频流" << std::endl;
break;
}
}
const AVCodec *avCodec = avcodec_find_decoder(avFormatContext->streams[audio_index]->codecpar->codec_id);
decoder_context = avcodec_alloc_context3(avCodec);
avcodec_parameters_to_context(decoder_context, avFormatContext->streams[audio_index]->codecpar);
ret = avcodec_open2(decoder_context, avCodec, nullptr);
if (ret < 0) {
std::cout << "音频解码器打开失败" << std::endl;
break;
}
while (true) {
ret = av_read_frame(avFormatContext, avPacket);
if (ret < 0) {
std::cout << "音频包读取完毕" << std::endl;
break;
}
if (avPacket->stream_index != audio_index) {
av_packet_unref(avPacket);
continue;
}
ret = avcodec_send_packet(decoder_context, avPacket);
if (ret < 0) {
std::cout << "音频包发送解码失败" << std::endl;
break;
}
while (true) {
ret = avcodec_receive_frame(decoder_context, avFrame);
if (ret == AVERROR(EAGAIN)) {
std::cout << "音频包获取解码帧:EAGAIN" << std::endl;
break;
} else if (ret < 0) {
std::cout << "音频包获取解码帧:fail" << std::endl;
break;
} else {
std::cout << "重新编码音频" << std::endl;
// 先进行重采样
resample_audio(avFrame);
pack_vector.clear();
encode_audio(pack_vector, out_frame);
while (!pack_vector.empty()) {
AVPacket *packet = pack_vector.at(0)