• ffmpeg学习笔记-音频播放


    前文讲到音频解码,将音频解码,并且输入到PCM文件,这里将音频通过AudioTrack直接输出

    音频播放说明

    在Android中自带的MediaPlayer也可以对音频播放,但其支持格式太少
    使用ffmpeg可以支持更多格式
    常用的音频播放有很多种方式,但播放PCM就只有OpenSL和AudioTrack
    这里使用AudioTrack进行实现
    在上文中已经实现了音频的解码,而在本文中,将对解码完成的音频进行播放
    在解码完成以后不是将其转化为PCM存储,而是直接进行播放
    为何要播放PCM格式音频,是因为喇叭最终输出的就是PCM数据

    代码示例

    PCMPlayer.java

    import android.media.AudioFormat;
    import android.media.AudioManager;
    import android.media.AudioTrack;
    
    public class PCMPlayer {
    
    	public native void sound(String input);
    	
    	public AudioTrack createAudioTrack(int sampleRateInHz, int channelConfig) {
    		//44100HZ 16bits 立体声
    		//int sampleRateInHz = 44100;
    		int audioFormat = AudioFormat.ENCODING_PCM_16BIT;
    		//int channelConfig = AudioFormat.CHANNEL_IN_STEREO;
    		int channelDefaultConfig;
    		if(channelConfig == 1){
    			channelDefaultConfig = android.media.AudioFormat.CHANNEL_OUT_MONO;
    		}else if(channelConfig == 2){
    			channelDefaultConfig = android.media.AudioFormat.CHANNEL_OUT_STEREO;
    		}else{
    			channelDefaultConfig = android.media.AudioFormat.CHANNEL_OUT_STEREO;
    		}
    		int bufferSizeInBytes = AudioTrack.getMinBufferSize(sampleRateInHz, channelConfig, audioFormat);
    		AudioTrack audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC,
    				sampleRateInHz, channelDefaultConfig, audioFormat, bufferSizeInBytes, AudioTrack.MODE_STREAM);
    		return audioTrack;
    	}
    	
    	static {
    		System.loadLibrary("avutil-54");
    		System.loadLibrary("swresample-1");
    		System.loadLibrary("avcodec-56");
    		System.loadLibrary("avformat-56");
    		System.loadLibrary("swscale-3");
    		System.loadLibrary("postproc-53");
    		System.loadLibrary("avfilter-5");
    		System.loadLibrary("avdevice-56");
    		System.loadLibrary("ffmpeg_pcm_player");
    	}
    }
    

    native实现

    #include <stdlib.h>
    #include <stdio.h>
    #include <unistd.h>
    #include <android/log.h>
    #include "com_cj5785_ffmpegpcmplayer_PCMPlayer.h"
    
    #include "include/libavformat/avformat.h"
    //解码
    #include "include/libavcodec/avcodec.h"
    //像素处理
    #include "include/libswscale/swscale.h"
    //重采样
    #include "include/libswresample/swresample.h"
    
    #define MAX_AUDIO_FRME_SIZE 48000 * 4
    #define LOGI(FORMAT,...) __android_log_print(4,"cj5785",FORMAT,##__VA_ARGS__);
    #define LOGE(FORMAT,...) __android_log_print(6,"cj5785",FORMAT,##__VA_ARGS__);
    
    JNIEXPORT void JNICALL Java_com_cj5785_ffmpegpcmplayer_PCMPlayer_sound
      (JNIEnv *env, jobject jobj, jstring jstr_input)
    {
    	const char *input = (*env)->GetStringUTFChars(env, jstr_input, NULL);
    
    	LOGI("%s",input);
    	//注册组件
    	av_register_all();
    
    	//打开输入文件
    	AVFormatContext *pFormatCtx = avformat_alloc_context();
    	if(avformat_open_input(&pFormatCtx, input, NULL, NULL) != 0)
    	{
    		LOGE("%s", "打开文件失败!");
    		return;
    	}
    
    	//获取流信息
    	if(avformat_find_stream_info(pFormatCtx, NULL) < 0)
    	{
    		LOGE("%s","获取输入文件信息失败!");
    		return;
    	}
    
    	//对输入流做音视频判断,获取音频流索引位置
    	int i = 0;
    	int audio_stream_index = -1;
    	for(; i < pFormatCtx->nb_streams; i++)
    	{
    		//判断是否是音频流
    		if(pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO)
    		{
    			audio_stream_index = i;
    			break;
    		}
    	}
    	if(audio_stream_index == -1)
    	{
    		LOGE("%s", "找不到音频流!");
    		return;
    	}
    
    	//获取解码器
    	AVCodecContext *codecCtx = pFormatCtx->streams[audio_stream_index]->codec;
    	AVCodec *codec = avcodec_find_decoder(codecCtx->codec_id);
    	if(codec == NULL)
    	{
    		LOGE("%s", "无法获取解码器");
    		return;
    	}
    
    	//打开解码器
    	if(avcodec_open2(codecCtx, codec, NULL) < 0)
    	{
    		LOGI("%s", "无法打开解码器");
    		return;
    	}
    
    	//压缩数据
    	AVPacket *packet = (AVPacket *)av_malloc(sizeof(AVPacket));
    	//解压缩数据
    	AVFrame *frame = av_frame_alloc();
    	//将压缩数据转化为16bits 44100Hz PCM 统一音频采样格式与采样率
    	SwrContext *swrCtx = swr_alloc();
    	//----------重采样设置参数----------
    	//输入采样格式
    	enum AVSampleFormat in_sample_fmt = codecCtx->sample_fmt;
    	//输出采样格式
    	enum AVSampleFormat out_sample_fmt = AV_SAMPLE_FMT_S16;
    	//输入采样率
    	int in_sample_rate = codecCtx->sample_rate;
    	//输出采样率
    	int out_sample_rate = in_sample_rate;
    	//获取输入的声道布局,根据声道个数获取声道布局 av_get_default_channel_layout(codec->channel_layouts);
    	uint64_t in_ch_layout = codecCtx->channel_layout;
    	//输出声道默认为立体声
    	uint64_t out_ch_layout = AV_CH_LAYOUT_STEREO;
    	swr_alloc_set_opts(swrCtx,
    			out_ch_layout, out_sample_fmt, out_sample_rate,
    			in_ch_layout, in_sample_fmt, in_sample_rate,
    			0, NULL);
    	swr_init(swrCtx);
    	//输出的声道个数
    	int nb_out_channel = av_get_channel_layout_nb_channels(out_ch_layout);
    	//----------重采样设置参数----------
    	//16bits 44100Hz PCM数据
    	uint8_t *out_buffer = (uint8_t *)av_malloc(MAX_AUDIO_FRME_SIZE);
    	//使用JNI调用Java
    	//获取到SoundPlayer对象
    	jclass player_class = (*env)->GetObjectClass(env, jobj);
    	//AudioTrack对象
    	jmethodID create_audio_track_mid = (*env)->GetMethodID(env, player_class, "createAudioTrack", "(II)Landroid/media/AudioTrack;");
    	jobject audio_track = (*env)->CallObjectMethod(env, jobj, create_audio_track_mid, in_sample_rate, in_ch_layout);
    	//AudioTrack的play方法
    	jclass audio_track_class = (*env)->GetObjectClass(env, audio_track);
    	jmethodID audio_track_play_mid = (*env)->GetMethodID(env, audio_track_class, "play", "()V");
    	(*env)->CallVoidMethod(env, audio_track, audio_track_play_mid);
    	//AudioTrack的write方法
    	jmethodID audio_track_write_mid = (*env)->GetMethodID(env, audio_track_class, "write", "([BII)I");
    	//不断读取压缩数据
    	int ret, got_frame = 0, frame_count = 0;
    	while(av_read_frame(pFormatCtx, packet) >= 0)
    	{
    		if(packet->stream_index == audio_stream_index)
    		{
    			ret = avcodec_decode_audio4(codecCtx, frame, &got_frame, packet);
    			if(ret < 0)
    			{
    				LOGI("%s","解码完成");
    			}
    			if(got_frame)
    			{
    				LOGI("解码第%d帧", frame_count++);
    				swr_convert(swrCtx, &out_buffer, MAX_AUDIO_FRME_SIZE, (const uint8_t **)frame->data, frame->nb_samples);
    				//获取sample的大小
    				int out_buffer_size = av_samples_get_buffer_size(NULL, nb_out_channel,
    						frame->nb_samples ,out_sample_fmt, 1);
    				//将PCM数据写入到AudioTrack中
    				//out_buffer转化为byte数组
    				jbyteArray audio_sample_array = (*env)->NewByteArray(env, out_buffer_size);
    				jbyte *sample_byte = (*env)->GetByteArrayElements(env, audio_sample_array, NULL);
    				//将out_buffer复制到sample_byte
    				memcpy(sample_byte, out_buffer, out_buffer_size);
    				//同步数据
    				(*env)->ReleaseByteArrayElements(env, audio_sample_array, sample_byte, 0);
    				//写入到AudioTrack中
    				(*env)->CallIntMethod(env, audio_track, audio_track_write_mid,
    						audio_sample_array, 0, out_buffer_size);
    				//释放局部引用
    				(*env)->DeleteLocalRef(env,audio_sample_array);
    				usleep(16 * 1000);
    			}
    		}
    		av_free_packet(packet);
    	}
    	av_frame_free(&frame);
    	av_free(out_buffer);
    	swr_free(&swrCtx);
    	avcodec_close(codecCtx);
    	avformat_close_input(&pFormatCtx);
    	(*env)->ReleaseStringUTFChars(env, jstr_input, input);
    }
    

    本文说明

    虽然实现了音频的播放,但依旧存在和之前视频一样的问题
    这个测试是放在主线程中去运行的,实际中应该放在子线程中去做
    放在主线程会造成线程的阻塞

  • 相关阅读:
    【原】Shell脚本-判断文件有无进而复制
    【原】个人对win7开机黑屏只有鼠标排障总结
    【原】window上安装elasticserach
    【原】CentOS7上安装Xwiki8.2.1
    Java集合中Map接口的使用方法
    Java集合中Set的常见问题及用法
    Java计时器Timer和TimerTask用法
    Java集合中List的用法
    Java RuntimeException异常处理汇总
    用Java计算某个日期100天后的日期
  • 原文地址:https://www.cnblogs.com/cj5785/p/10664656.html
Copyright © 2020-2023  润新知