Qt+FFmpeg+opengl从零制作视频播放器-4.音频解码

时间:2024-03-14 12:09:04

首先一个完整的MP4文件解封装之后,得到了压缩的音频数据,这个数据是不能直接拿去播放的,我们需要解码成原始的PCM数据才能够播放,解码音频数据,如下图所示,把MP3或者AAC数据解码成原始的数据pcm。

音频解码是将编码的音频数据(如MP3, AAC, OGG等格式)转换为可以播放的PCM(脉冲编码调制)数据的过程。这个过程通常涉及以下步骤:

  • 解封装(Demuxing):

    • 从容器格式(如MP4, MKV, AVI等)中分离出音频数据流。
    • 读取音频流的元数据,包括编码类型、采样率、通道数、比特率等。
  • 解码准备:

    • 初始化解码器。找到与音频流匹配的解码器(例如:libmp3lame解码MP3数据流)。
    • 打开解码器,准备开始解码。
  • 循环解码:

    • 从分离出的音频数据流中读取编码的音频数据包(packet)。
    • 将编码的数据包发送到解码器进行解码。
    • 从解码器中接收解码后的帧数据(解码器可能需要多个数据包才能生成一个完整的帧)。
  • 帧处理:

    • 将解码出来的帧(PCM数据)进行可能的后处理,例如重采样(如果需要改变采样率)、声道转换(比如立体声到单声道)、音量调整等。
    • 处理后的帧数据准备播放或进一步处理。
  • 同步和播放:

    • 如果需要与视频同步,采取相应的机制确保音频和视频能够同步播出。
    • 将解码、处理后的音频数据送至音频输出设备播放。
  • 流的结束:

    • 处理音频流的结束,这可能涉及刷新解码器以输出最后几帧,关闭解码器和清理资源。

ffmpeg解码音频的数据步骤:首先前面的解封装步骤不能少。

Qt+FFmpeg+opengl从零制作视频播放器-3.解封装

查找解码器,根据音频流的codec_id找到解码器。


    //找到解码器
    AVCodec *codec = avcodec_find_decoder(st->codecpar->codec_id);
    if (!codec)
    {
        fprintf(stderr, "Codec not found\n");
        exit(1);
    }

申请AVCodecContenxt上下文。


    //申请AVCodecContext
    AVCodecContext* m_pCodecCtx= nullptr;
    m_pCodecCtx = avcodec_alloc_context3(codec);
    if (!codec_ctx)
    {
        exit(1);
    }

配置解码器上下文参数。

	///配置解码器上下文参数
	avcodec_parameters_to_context(m_pCodecCtx, para);

打开解码器。

	int ret = avcodec_open2(m_pCodecCtx, 0, 0);
	if (ret != 0)
	{
		avcodec_free_context(&m_pCodecCtx);
		cout << "avcodec_open2  failed! :" << buf << endl;
	}

然后通过while循环,不停的读取数据,解码。

av_read_frame(inputFmtCtx, pkt)
 
 
avcodec_send_packet(m_pCodecCtx, pkt);
 
avcodec_receive_frame(m_pCodecCtx, frame);

执行结束后关闭输入文件,释放资源。

    //关闭
    avformat_close_input(&inputFmtCtx);
 
    //释放
    avformat_free_context(inputFmtCtx);
 
    //释放资源
    av_packet_free(&pkt);

AVCodecParameters 用于保存音视频流的基本参数信息,音频相关的成员变量解析:

  • codec_type:这是一个枚举类型AVMediaType,用于指定编解码器的类型。对于音频来说,可以是AVMEDIA_TYPE_AUDIO,表示音频数据。
  • codec_id:这个枚举类型的成员变量指定了编码格式,例如MP3、AAC等音频编码格式。
  • format:这个成员变量对于音频来说指的是采样格式,如16位PCM、32位浮点等。
  • channels:这个成员变量表示音频的通道数,即单声道、立体声或多声道等。
  • sample_rate:这个成员变量表示音频的采样率,即每秒钟采样的次数,通常以Hz为单位。
  • channel_layout:这个成员变量指定了音频通道的布局,如立体声、环绕声等。
        int ret = avformat_open_input(&m_pFormatCtx, url, NULL, &opts);
        ...
        ...
        ...
		m_sampleRate = m_pFormatCtx->streams[m_audioIndex]->codec->sample_rate;
		m_channels = m_pFormatCtx->streams[m_audioIndex]->codec->channels;
		m_aTimeBase = r2d(m_pFormatCtx->streams[m_audioIndex]->time_base);

		cout << "=======================================================" << endl;
		cout << m_audioIndex << " audio info" << endl;
		cout << "codec_id = " << m_pFormatCtx->streams[m_audioIndex]->codecpar->codec_id << endl;
		cout << "format = " << m_pFormatCtx->streams[m_audioIndex]->codecpar->format << endl;
		cout << "sample_rate = " << m_sampleRate << endl;
		cout << "channels = " << m_channels << endl;
		cout << "=======================================================" << endl;

源码示例:保存音频前200帧数据。

#include <QtCore/QCoreApplication>


extern "C"
{
#include "libavformat/avformat.h"
#include "libavutil/dict.h"
#include "libavutil/opt.h"
#include "libavutil/timestamp.h"
#include "libswscale/swscale.h"
#include "libswresample/swresample.h"
#include "libavutil/imgutils.h" 
};


int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

	//av_register_all();
	avformat_network_init();

	AVFormatContext* ifmt_ctx = NULL;
	const char* inputUrl = "F:/1920x1080.mp4";

	///打开输入的流
	int ret = avformat_open_input(&ifmt_ctx, inputUrl, NULL, NULL);
	if (ret != 0)
	{
		printf("Couldn't open input stream.\n");
		return -1;
	}

	//查找流信息
	if (avformat_find_stream_info(ifmt_ctx, NULL) < 0)
	{
		printf("Couldn't find stream information.\n");
		return -1;
	}

	//找到音频流索引
	int audio_index = av_find_best_stream(ifmt_ctx, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0);

	AVStream* st = ifmt_ctx->streams[audio_index];

	AVCodec* codec = nullptr;

	//找到解码器
	codec = avcodec_find_decoder(st->codecpar->codec_id);
	if (!codec)
	{
		fprintf(stderr, "Codec not found\n");
		exit(1);
	}

	//申请AVCodecContext
	AVCodecContext* codec_ctx = nullptr;
	codec_ctx = avcodec_alloc_context3(codec);
	if (!codec_ctx)
	{
		exit(1);
	}

	avcodec_parameters_to_context(codec_ctx, ifmt_ctx->streams[audio_index]->codecpar);

	//打开解码器
	if ((ret = avcodec_open2(codec_ctx, codec, NULL) < 0))
	{
		return -1;
	}

	AVPacket* pkt = av_packet_alloc();
	//av_init_packet(pkt);

	AVFrame *frame = av_frame_alloc();

	char fileName[20] = "test.pcm";

	//保存pcm文件
	FILE* f;
	f = fopen(fileName, "wb");

	static int frameCount = 0;

	//不断读取数据
	while (av_read_frame(ifmt_ctx, pkt) >= 0)
	{
		if (pkt->stream_index == audio_index)
		{
			int ret = avcodec_send_packet(codec_ctx, pkt);
			if (ret >= 0)
			{
				ret = avcodec_receive_frame(codec_ctx, frame);
				if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
				{
					continue;
				}
				else if (ret < 0)
				{
					continue;
				}
				//保存200帧数据
				if(frameCount >= 200)
					break;

				//获取数据大小
				int data_size = av_get_bytes_per_sample(codec_ctx->sample_fmt);
				if (data_size < 0) 
				{
					continue;
				}
				for (int i = 0; i < frame->nb_samples; i++)
				{
					for (int ch = 0; ch < codec_ctx->channels; ch++)
					{
						fwrite(frame->data[ch] + data_size * i, 1, data_size, f);
					}
				}
			}

			frameCount++;
		}
	}

	fclose(f);

	printf("write finished\n");

	avcodec_close(codec_ctx);
	avcodec_free_context(&codec_ctx);
	avformat_close_input(&ifmt_ctx);
	av_frame_free(&frame);
	av_packet_free(&pkt);

    return a.exec();
}

使用pcm数据工具,用于播放pcm文件。

pcm工具pcm工具pcm工具-C++文档类资源-CSDN下载

使用pcm工具播放 保存好的pcm文件。

选择导入原始数据,点击Detect按钮,自动获取pcm的格式。

FFmpeg是一个多功能的多媒体处理工具,它用于转换、编码、解码、转码等多种任务。以下是一些常用的FFmpeg命令:

获取视频信息:`ffmpeg -i [输入文件名]`来获取视频的详细信息。
视频格式转换:例如,将MP4格式的视频转换为FLV格式,可以使用命令`ffmpeg -i input.mp4 -f flv output.flv`。
音频格式转换:将MP3格式的音频转换为PCM格式,可以使用命令`ffmpeg -i input.mp3 -f s16be -ar 16000 -ac 1 -acodec pcm_s16be output.pcm`。
音视频分离:使用命令`ffmpeg -i input.mp4 -vcodec copy -an output.mp4`来去除音频,只保留视频。
截取视频:使用命令`ffmpeg -i input.mp4 -ss 8 -t 2 -s 1280x720 -codec copy -f flv output.flv`来截取一段视频。
音视频同步:使用命令`ffmpeg -i input.mp4 -ss 8 -t 2 -i input.aac -c copy -map 0:v:0 -map 1:a:0 output.mp4`来将视频和音频同步。
音视频编码:使用命令`ffmpeg -i input.mp4 -c:v libx264 -preset ultrafast -crf 22 -c:a aac -b:a 128k output.mp4`来对视频和音频进行编码。

完整工程: 

https://download.csdn.net/download/wzz953200463/88959152https://download.csdn.net/download/wzz953200463/88959152