FFmpeg 从零开始开发简单的音视频播放器(六)

添加音频解码功能

一、前言

        前面第四节“FFmpeg 从零开始开发简单的音视频播放器(四)”,进行了视频的解码和转码,我们这节就在该基础之上,添加音频解码功能。

二、代码

// FFmpegDll.cpp: 定义控制台应用程序的入口点。
//

#include "stdafx.h"
#include "FFmpegDll.h"

extern "C" {
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"  
#include "libswscale/swscale.h"  
#include "libavutil/imgutils.h"  
#include "libswresample/swresample.h"  
}

AVFormatContext	*fmt_ctx;

//流队列中,视频流所在的位置
int video_index = -1;
int audio_index = -1;

//解码上下文
AVCodecContext	*video_codec_ctx;
AVCodecContext	*audio_codec_ctx;

//输出缓存大小
int video_out_buffer_size;
int audio_out_buffer_size;

//输出缓存
uint8_t *video_out_buffer;
uint8_t *audio_out_buffer;

//转码后输出的视频帧(如yuv转rgb24)
AVFrame	*video_out_frame = av_frame_alloc();

//格式转换上下文
struct SwsContext *video_convert_ctx;
struct SwrContext *audio_convert_ctx;

//解码前数据包
AVPacket *packet = (AVPacket *)malloc(sizeof(AVPacket));

//音频声道数量
int nb_channels;

enum AVSampleFormat out_sample_fmt = AV_SAMPLE_FMT_S16;//输出的采样格式 16bit PCM
int sample_rate;//采样率
uint64_t out_ch_layout = AV_CH_LAYOUT_STEREO;//输出的声道布局:立体声

//初始化FFmpeg 
//@param *url 媒体地址(本地/网络地址)
int init_ffmpeg(char *url) {
	av_register_all();//注册组件
	avformat_network_init();//支持网络流
	fmt_ctx = avformat_alloc_context();

	//打开文件
	if (avformat_open_input(&fmt_ctx, url, NULL, NULL) != 0) {
		return -1;
	}

	//查找流信息
	if (avformat_find_stream_info(fmt_ctx, NULL) < 0)
	{
		return -1;
	}

	//找到流队列中,视频流所在位置
	for (int i = 0; i < fmt_ctx->nb_streams; i++) {
		if (fmt_ctx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO)
		{
			video_index = i;
		}
		if (fmt_ctx->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO)
		{
			audio_index = i;
		}
	}

	//音视频流都没有找到
	if (video_index == -1 && audio_index == -1)
	{
		return -1;
	}

	//查找解码器
	video_codec_ctx = fmt_ctx->streams[video_index]->codec;
	audio_codec_ctx = fmt_ctx->streams[audio_index]->codec;
	AVCodec	*video_codec = avcodec_find_decoder(video_codec_ctx->codec_id);
	AVCodec	*audio_codec = avcodec_find_decoder(audio_codec_ctx->codec_id);

	//解码器没有找到
	if (video_codec == NULL && audio_codec == NULL)
	{
		return -1;
	}

	//打开解码器
	if (avcodec_open2(video_codec_ctx, video_codec, NULL) < 0 & //两个都要打开,用&
		avcodec_open2(audio_codec_ctx, audio_codec, NULL) < 0)
	{
		return -1;
	}

	//======视频转码准备======start======
	//计算输出缓存
	video_out_buffer_size = av_image_get_buffer_size(
		AV_PIX_FMT_RGB24,
		video_codec_ctx->width,
		video_codec_ctx->height,
		1);
	
	//输出缓存
	video_out_buffer = new uint8_t[video_out_buffer_size];

	//准备一些参数,在视频格式转换后,参数将被设置值
	av_image_fill_arrays(
		video_out_frame->data,//转换后的数据
		video_out_frame->linesize,
		video_out_buffer, //视频buffer
		AV_PIX_FMT_RGB24,//像素格式
		video_codec_ctx->width,
		video_codec_ctx->height,
		1);

	video_convert_ctx = sws_getContext(//图片格式转换上下文
		video_codec_ctx->width,
		video_codec_ctx->height,
		video_codec_ctx->pix_fmt,
		video_codec_ctx->width,
		video_codec_ctx->height,
		AV_PIX_FMT_RGB24,//转码为RGB像素
		SWS_BICUBIC,
		NULL, NULL, NULL);
	//======视频转码准备======end======

	//======音频转码准备======start======
	enum AVSampleFormat in_sample_fmt = audio_codec_ctx->sample_fmt;//输入的采样格式
	sample_rate = audio_codec_ctx->sample_rate;//输入的采样率
	uint64_t in_ch_layout = audio_codec_ctx->channel_layout;//输入的声道布局

	audio_convert_ctx = swr_alloc();
	swr_alloc_set_opts(audio_convert_ctx, out_ch_layout, out_sample_fmt, sample_rate, in_ch_layout, in_sample_fmt, sample_rate, 0, NULL);
	swr_init(audio_convert_ctx);

	nb_channels = av_get_channel_layout_nb_channels(out_ch_layout);//获取声道个数
	audio_out_buffer = (uint8_t *)av_malloc(sample_rate * 2);//存储pcm数据
	//======音频转码准备======end======

	av_init_packet(packet);
	return 0;
}

//读取一帧 -1:未取到 1:音频 2:视频 
int read_frame() {
	int ret = -1;

	//是否从packet中解出一帧,0为未解出
	int got_picture;
	int got_frame;

	//从packet中解出来的原始音视频帧
	AVFrame	*original_audio_frame = av_frame_alloc();
	AVFrame	*original_video_frame = av_frame_alloc();

	if (av_read_frame(fmt_ctx, packet) == 0) {
		if (packet->stream_index == video_index)
		{
			//解码。输入为packet,输出为original_video_frame
			if (avcodec_decode_video2(video_codec_ctx, original_video_frame, &got_picture, packet) >= 0)
			{
				if (got_picture)
				{
					//图片格式转换(上面图片转换准备的参数,在这里使用)
					sws_scale(video_convert_ctx,//图片转码上下文
						(const uint8_t* const*)original_video_frame->data,//原始数据
						original_video_frame->linesize,//原始参数
						0,//转码开始游标,一般为0
						video_codec_ctx->height,//行数
						video_out_frame->data,//转码后的数据
						video_out_frame->linesize);
					ret = 2;
					//转码后,将buffer拷贝到非托管区内存,其他地方可以读取该内存;
					//memcpy(video_buffer, video_out_buffer, video_out_buffer_size);
				}
			}
		}
		else if (packet->stream_index == audio_index)
		{
			if (avcodec_decode_audio4(audio_codec_ctx, original_audio_frame, &got_frame, packet) >= 0)
			{
				if (got_frame)
				{
					//音频格式转换
					swr_convert(audio_convert_ctx,//音频转换上下文
						&audio_out_buffer,//输出缓存
						sample_rate * 2,//每次输出大小
						(const uint8_t **)original_audio_frame->data,//输入数据
						original_audio_frame->nb_samples);//输入

					audio_out_buffer_size = av_samples_get_buffer_size(NULL, nb_channels, original_audio_frame->nb_samples, out_sample_fmt, 1);
					ret = 1;
				}
			}
		}
	}

	av_free_packet(packet);
	av_free(original_audio_frame);
	av_free(original_video_frame);
	return ret;
}

//获取音频缓存大小
int get_audio_buffer_size() {
	return audio_out_buffer_size;
}

//获取视频缓存大小
int get_video_buffer_size() {
	return video_out_buffer_size;
}

//获取音频帧
char *get_audio_frame() {
	return (char *)audio_out_buffer;
}

//获取视频帧
char *get_video_frame() {
	return (char *)video_out_buffer;
}

//获取视频宽度
int get_video_width() {
	return video_codec_ctx->width;
}

//获取视频高度
int get_video_height() {
	return video_codec_ctx->height;
}

//释放资源
void release() {
	sws_freeContext(video_convert_ctx);
	swr_free(&audio_convert_ctx);

	av_free(video_out_frame);

	av_free(video_out_buffer);
	av_free(audio_out_buffer);

	avcodec_close(video_codec_ctx);
	avcodec_close(audio_codec_ctx);

	avformat_close_input(&fmt_ctx);
}


int main()
{
	char url[] = "rtmp://live.hkstv.hk.lxdns.com/live/hks";
	int init_ret = init_ffmpeg(url);
	if (init_ret >= 0)
	{
		int read = -1;
		while ((read = read_frame()) != 1)//读取一帧,直到读取到数据
		{
			printf("未读取到数据\n");
		}
		//printf("高度:%d  宽度:%d  缓存大小:%d", get_video_height(), get_video_width(), get_video_buffer_size());
		//printf("\n=================================\n");
		//printf(get_video_frame());//打印出来,虽然打印出来的东西看不懂,但是证明已经获取到一帧的数据了

		while ((read = read_frame()) > 0)//循环读取音频
		{
			if (read == 1)
			{
				printf("缓存大小:%d", get_audio_buffer_size());
				printf("\n=================================\n");
				printf(get_audio_frame());
				printf("\n=================================\n");
			}
		}
	}
	else
	{
		printf("初始化失败!");
	}
	getchar();
    return 0;
}

        对音频的解码,同视频解码转码差不多,代码里对解码做了注释说明,大家应该都能看懂。

三、运行项目

        1、右击c++项目-->设为启动项目

FFmpeg 从零开始开发简单的音视频播放器(六)_第1张图片

--------------------------------------------------------------------

        2、将dll设置为exe:右击c++项目-->属性-->常规-->项目默认-->配置类型-->应用程序

FFmpeg 从零开始开发简单的音视频播放器(六)_第2张图片

---------------------------------------------------------------------------------------

        3、重新生成并运行项目:

FFmpeg 从零开始开发简单的音视频播放器(六)_第3张图片

---------------------------------------------------------------------------

        从运行结果中,可以看出,循环打印出了单次音频解码后的缓存大小为4096字节,还有一些看不懂的字符(解码后的音频),证明音频解码已经完成,下一节我们将把音频解码一并加入c#项目中,完成最后的开发。

你可能感兴趣的:(wpf,c++,FFmpeg,c#)