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);
}
本文说明
虽然实现了音频的播放,但依旧存在和之前视频一样的问题
这个测试是放在主线程中去运行的,实际中应该放在子线程中去做
放在主线程会造成线程的阻塞
ffmpeg学习笔记-音频播放的更多相关文章
- ffmpeg学习笔记-音频解码
在之前的文章已经初步对视频解码有个初步的认识了,接下来来看一看音频解码 音频解码步骤 音频解码与视频解码一样,有者固有的步骤,只要按照步骤来,就能顺利的解码音频 以上是ffmpeg的解码流程图,可以看 ...
- 最简单的基于FFMPEG+SDL的音频播放器 ver2 (采用SDL2.0)
===================================================== 最简单的基于FFmpeg的音频播放器系列文章列表: <最简单的基于FFMPEG+SDL ...
- 最简单的基于FFMPEG+SDL的音频播放器 ver2 (採用SDL2.0)
===================================================== 最简单的基于FFmpeg的音频播放器系列文章列表: <最简单的基于FFMPEG+SDL ...
- FFmpeg学习3:播放音频
参考dranger tutorial,本文将介绍如何使用FFmpeg解码音频数据,并使用SDL将解码后的数据输出. 本文主要包含以下几方面的内容: 关于播放音频的需要的一些基础知识介绍 使用SDL2播 ...
- Android应用开发学习笔记之播放音频
作者:刘昊昱 博客:http://blog.csdn.net/liuhaoyutz Android支持常用音视频格式文件的播放,本文我们来学习怎样开发Android应用程序对音视频进行操作. Andr ...
- FFmpeg基础库编程开发学习笔记——音频常见格式及字幕格式
声明一下:这些关于ffmpeg的文章仅仅是用于记录我的学习历程和以便于以后查阅,文章中的一些文字可能是直接摘自于其它文章.书籍或者文献,学习ffmpeg相关知识是为了使用在Android上,我也才是刚 ...
- ffmpeg学习笔记
对于每一个刚開始学习的人,刚開始接触ffmpeg时,想必会有三个问题最为关心,即ffmpeg是什么?能干什么?怎么開始学习?本人前段时间開始接触ffmpeg,在刚開始学习过程中.这三个问 ...
- 【音乐App】—— Vue-music 项目学习笔记:播放器内置组件开发(二)
前言:以下内容均为学习慕课网高级实战课程的实践爬坑笔记. 项目github地址:https://github.com/66Web/ljq_vue_music,欢迎Star. 播放模式切换 歌词滚动显示 ...
- ffmpeg学习笔记-多线程音视频解码
之前的视频解码仍然存在问题,那就是是在主线程中去完成解码的,会造成线程阻塞,这里将其改为多线程解码,使其主线程不被阻塞 前面介绍了音视频的主线程解码,那样会阻塞主线程,在前面学习了多线程以后,就可以对 ...
随机推荐
- 边学边体验django
django菜鸟入门连接:http://www.runoob.com/django/django-template.html 安装以及Demo里面都有. 我边学边在电脑上演示,然后发现一些和实际不一样 ...
- Oracle - 特殊字符问题
Oracle更新表字段或者查询表字段时内容中含有特殊字符&的解决方法 现象 解决方式 1. 字符串拼接 UPDATE T_MENU_INFO SET menu_code='/ABeptjk/g ...
- Base64().encodeBase64Chunked导致换行符的问题
String linkStr=new String(new Base64().encodeBase64Chunked(new String("conferid="+cid+&quo ...
- HashMap判断键是否为null
用containsKey(),而不用get(): HashMap中,null可以作为键,这样的键只有一个:可以有一个或多个键所对应的值为null.当get()方法返回null值时,即可以表示HashM ...
- MultipartFile 类
本文链接:https://blog.csdn.net/Peter_S/article/details/84951978今天说下 MultipartFile 这个类吧,这两天正在用,也在网上查了查资料, ...
- mac 安装软件 显示信任任何来源
“通用”里有时没有“任何来源”这个选项: 显示"任何来源"选项在控制台中执行: sudo spctl --master-disable 不显示"任何来源"选项( ...
- 简单删除我的电脑里的wps云文档图标
装个wps,用着都挺好,我的电脑一直存在wps云文档的图标. 看久了就觉得很膈应,那就直接干掉吧. 桌面新建一个文本文件,选中新建文本文档.txt 按f2 然后修改为11.reg(任意名称只要保证后缀 ...
- 1-7HSB色彩模式
http://www.missyuan.com/thread-350721-1-1.html HSB色彩模式色相hue.饱和度saturation.明度brightness 在HSB模式中,S和B的取 ...
- react-native 打包apk 更新js和常见问题
1.打包发布 a.在工程根目录执行如下命令 打包js资源文件 react-native bundle --platform android --dev false --entry-file index ...
- springboot+mybatis+druid+sqlite/mysql/oracle
搭建springboot+mybatis+druid+sqlite/mysql/oracle附带测试 1.版本 springboot2.1.6 jdk1.8 2.最简springboot环境 http ...