PCM(Pulse Code Modulation),脈衝編碼調製。人耳聽到的是模擬信號,PCM是把聲音從模擬信號轉化爲數字信號的技術。原理是用一個固定的頻率對模擬信號進行採樣,採樣後的信號在波形上看就像一串連續的幅值不一的脈衝(脈搏似的短暫起伏的電衝擊),把這些脈衝的幅值按一定精度進行量化,這些量化後的數值被連續的輸出、傳輸、處理或記錄到存儲介質中,所有這些組成了數字音頻的產生過程(抽樣、量化、編碼三個過程)。
播放音樂時,應用程序從存儲介質中讀取音頻數據(MP3、WMA、AAC…),經過解碼後,最終送到音頻驅動程序中的就是PCM數據,反過來,在錄音時,音頻驅動不停地把採樣所得的PCM數據送回給應用程序,由應用程序完成壓縮、存儲等任務。
下面我們通過ffmpeg音頻轉原生的PCM
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void play(View view) {
player();
}
private static String[] permissions = new String[]{
Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.READ_EXTERNAL_STORAGE
};
private void player(){
String[] permissions1 = checkPermission(this);
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.M){
if(permissions1.length<=0){
MyPlayer myPlayer = new MyPlayer();
String input = new File(Environment.getExternalStorageDirectory(),"input.mp3").getAbsolutePath();
String output = new File(Environment.getExternalStorageDirectory(),"output.pcm").getAbsolutePath();
myPlayer.sound(input,output);
}else{
ActivityCompat.requestPermissions(this, permissions, 100);
}
}else{//6.0以下不需要申請權限
MyPlayer myPlayer = new MyPlayer();
String input = new File(Environment.getExternalStorageDirectory(),"input.mp3").getAbsolutePath();
String output = new File(Environment.getExternalStorageDirectory(),"output.pcm").getAbsolutePath();
myPlayer.sound(input,output);
}
}
public static String[] checkPermission(Context context){
List<String> data = new ArrayList<>();//存儲未申請的權限
for (String permission : permissions) {
int checkSelfPermission = ContextCompat.checkSelfPermission(context, permission);
if(checkSelfPermission == PackageManager.PERMISSION_DENIED){//未申請
data.add(permission);
}
}
return data.toArray(new String[data.size()]);
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if(requestCode == 100){
boolean flag = true;
for(int i : grantResults){
if(i != PackageManager.PERMISSION_GRANTED){
flag = false;
break;
}
}
if(flag){
player();
}else{
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
}else{
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
}
}
public class MyPlayer {
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("native-lib");
}
public native void sound(String input,String output);
}
核心代碼
#include <jni.h>
#include <string>
#include <android/log.h>
#define LOGI(FORMAT,...) __android_log_print(ANDROID_LOG_INFO,"twy",FORMAT,##__VA_ARGS__);
#define LOGE(FORMAT,...) __android_log_print(ANDROID_LOG_ERROR,"twy",FORMAT,##__VA_ARGS__);
#define MAX_AUDIO_FRME_SIZE 48000 * 4
extern "C"{
//封裝格式
#include "libavformat/avformat.h"
//解碼
#include "libavcodec/avcodec.h"
//縮放
#include "libswscale/swscale.h"
//重採樣
#include "libswresample/swresample.h"
};
extern "C"
{
JNIEXPORT void JNICALL
Java_com_dongnao_ffmpegmusicdemo_MyPlayer_sound(JNIEnv *env, jobject instance, jstring input_,
jstring output_) {
const char *input = env->GetStringUTFChars(input_, 0);
const char *output = env->GetStringUTFChars(output_, 0);
av_register_all();
AVFormatContext *pFormatCtx = avformat_alloc_context();
//打開音頻文件
if(avformat_open_input(&pFormatCtx,input,NULL,NULL) != 0){
LOGI("%s","無法打開音頻文件");
return;
}
//獲取輸入文件信息
if(avformat_find_stream_info(pFormatCtx,NULL) < 0){
LOGI("%s","無法獲取輸入文件信息");
return;
}
//獲取音頻流索引位置
int i = 0, audio_stream_idx = -1;
for(; i < pFormatCtx->nb_streams;i++){
if(pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO){
audio_stream_idx = i;
break;
}
}
//獲取解碼器
AVCodecContext *codecCtx = pFormatCtx->streams[audio_stream_idx]->codec;
AVCodec *codec = avcodec_find_decoder(codecCtx->codec_id);
//打開解碼器
if(avcodec_open2(codecCtx,codec,NULL) < 0){
LOGI("%s","無法打開解碼器");
return;
}
//壓縮數據
AVPacket *packet = (AVPacket *)av_malloc(sizeof(AVPacket));
//解壓縮數據
AVFrame *frame = av_frame_alloc();
//frame->16bit 44100 PCM 統一音頻採樣格式與採樣率
SwrContext *swrContext = swr_alloc();
// 音頻格式 重採樣設置參數
AVSampleFormat in_sample = codecCtx->sample_fmt;
// 輸出採樣格式
AVSampleFormat out_sample=AV_SAMPLE_FMT_S16;
// 輸入採樣率
int in_sample_rate = codecCtx->sample_rate;
// 輸出採樣
int out_sample_rate=44100;
// 輸入聲道佈局
uint64_t in_ch_layout=codecCtx->channel_layout;
// 輸出聲道佈局
uint64_t out_ch_layout = AV_CH_LAYOUT_STEREO;
swr_alloc_set_opts(swrContext,out_ch_layout,out_sample,out_sample_rate
,in_ch_layout,in_sample,in_sample_rate,0,NULL);
swr_init(swrContext);
int got_frame=0;
int ret;
int out_channerl_nb = av_get_channel_layout_nb_channels(out_ch_layout);
LOGE("聲道數量%d ",out_channerl_nb);
int count=0;
// 設置音頻緩衝區間 16bit 44100 PCM數據
uint8_t *out_buffer = (uint8_t *) av_malloc(2 * 44100);
FILE *fp_pcm = fopen(output, "wb");
while (av_read_frame(pFormatCtx, packet)>=0) {
ret = avcodec_decode_audio4(codecCtx, frame, &got_frame, packet);
LOGE("正在解碼%d",count++);
if (ret < 0) {
LOGE("解碼完成");
}
// 解碼一幀
if (got_frame > 0) {
swr_convert(swrContext, &out_buffer, 2 * 44100,
(const uint8_t **) frame->data, frame->nb_samples);
int out_buffer_size=av_samples_get_buffer_size(NULL, out_channerl_nb, frame->nb_samples, out_sample, 1);
fwrite(out_buffer, 1, out_buffer_size,fp_pcm);
}
av_free_packet(packet);
}
fclose(fp_pcm);
av_frame_free(&frame);
av_free(out_buffer);
swr_free(&swrContext);
avcodec_close(codecCtx);
avformat_close_input(&pFormatCtx);
}
}
Native 層回調播放
public class MyPlayer {
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("native-lib");
}
private AudioTrack audioTrack;
public native void sound(String input,String output);
// 這個方法 是C進行調用 通道數
public void createAudio(int sampleRateInHz,int nb_channals) {
int channaleConfig;
if (nb_channals == 1) {
channaleConfig = AudioFormat.CHANNEL_OUT_MONO;
} else if (nb_channals == 2) {
channaleConfig = AudioFormat.CHANNEL_OUT_STEREO;
}else {
channaleConfig = AudioFormat.CHANNEL_OUT_MONO;
}
int buffersize= AudioTrack.getMinBufferSize(sampleRateInHz,
channaleConfig, AudioFormat.ENCODING_PCM_16BIT);
audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC,sampleRateInHz,channaleConfig,
AudioFormat.ENCODING_PCM_16BIT,buffersize,AudioTrack.MODE_STREAM);
audioTrack.play();
}
//C傳入音頻數據
public synchronized void playTrack(byte[] buffer, int lenth) {
if (audioTrack != null && audioTrack.getPlayState() == AudioTrack.PLAYSTATE_PLAYING) {
audioTrack.write(buffer, 0, lenth);//寫入進來喇叭就直接播放了
}
}
}
#include <jni.h>
#include <string>
#include <android/log.h>
#define LOGI(FORMAT,...) __android_log_print(ANDROID_LOG_INFO,"twy",FORMAT,##__VA_ARGS__);
#define LOGE(FORMAT,...) __android_log_print(ANDROID_LOG_ERROR,"twy",FORMAT,##__VA_ARGS__);
#define MAX_AUDIO_FRME_SIZE 48000 * 4
extern "C"{
//封裝格式
#include "libavformat/avformat.h"
//解碼
#include "libavcodec/avcodec.h"
//縮放
#include "libswscale/swscale.h"
//重採樣
#include "libswresample/swresample.h"
};
extern "C"
{
JNIEXPORT void JNICALL
Java_com_dongnao_ffmpegmusicdemo_MyPlayer_sound(JNIEnv *env, jobject instance, jstring input_,
jstring output_) {
const char *input = env->GetStringUTFChars(input_, 0);
const char *output = env->GetStringUTFChars(output_, 0);
av_register_all();
AVFormatContext *pFormatCtx = avformat_alloc_context();
//打開音頻文件
if(avformat_open_input(&pFormatCtx,input,NULL,NULL) != 0){
LOGI("%s","無法打開音頻文件");
return;
}
//獲取輸入文件信息
if(avformat_find_stream_info(pFormatCtx,NULL) < 0){
LOGI("%s","無法獲取輸入文件信息");
return;
}
//獲取音頻流索引位置
int i = 0, audio_stream_idx = -1;
for(; i < pFormatCtx->nb_streams;i++){
if(pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO){
audio_stream_idx = i;
break;
}
}
//獲取解碼器
AVCodecContext *codecCtx = pFormatCtx->streams[audio_stream_idx]->codec;
AVCodec *codec = avcodec_find_decoder(codecCtx->codec_id);
//打開解碼器
if(avcodec_open2(codecCtx,codec,NULL) < 0){
LOGI("%s","無法打開解碼器");
return;
}
//壓縮數據
AVPacket *packet = (AVPacket *)av_malloc(sizeof(AVPacket));
//解壓縮數據
AVFrame *frame = av_frame_alloc();
//音頻轉換上下文 frame->16bit 44100 PCM 統一音頻採樣格式與採樣率
SwrContext *swrContext = swr_alloc();
// 音頻格式 重採樣設置參數
AVSampleFormat in_sample = codecCtx->sample_fmt;
// 輸出採樣格式
AVSampleFormat out_sample=AV_SAMPLE_FMT_S16;
// 輸入採樣率
int in_sample_rate = codecCtx->sample_rate;
// 輸出採樣
int out_sample_rate=44100;
// 輸入聲道佈局
uint64_t in_ch_layout=codecCtx->channel_layout;
// 輸出聲道佈局
uint64_t out_ch_layout = AV_CH_LAYOUT_STEREO;
swr_alloc_set_opts(swrContext,out_ch_layout,out_sample,out_sample_rate
,in_ch_layout,in_sample,in_sample_rate,0,NULL);
swr_init(swrContext);
// 獲取通道數 2
int out_channer_nb = av_get_channel_layout_nb_channels(AV_CH_LAYOUT_STEREO);
// 反射得到Class類型
jclass david_player = env->GetObjectClass(instance);
// 反射得到createAudio方法
jmethodID createAudio = env->GetMethodID(david_player, "createAudio", "(II)V");
// 反射調用createAudio
env->CallVoidMethod(instance, createAudio, 44100, out_channer_nb);
jmethodID audio_write = env->GetMethodID(david_player, "playTrack", "([BI)V");
int got_frame=0;
int ret;
int out_channerl_nb = av_get_channel_layout_nb_channels(out_ch_layout);
LOGE("聲道數量%d ",out_channerl_nb);
int count=0;
// 設置音頻緩衝區間 16bit 44100 PCM數據
uint8_t *out_buffer = (uint8_t *) av_malloc(2 * 44100);
FILE *fp_pcm = fopen(output, "wb");
while (av_read_frame(pFormatCtx, packet)>=0) {
ret = avcodec_decode_audio4(codecCtx, frame, &got_frame, packet);
LOGE("正在解碼%d",count++);
if (ret < 0) {
LOGE("解碼完成");
}
// 解碼一幀
if (got_frame > 0) {
swr_convert(swrContext, &out_buffer, 2 * 44100,
(const uint8_t **) frame->data, frame->nb_samples);
int out_buffer_size=av_samples_get_buffer_size(NULL, out_channerl_nb, frame->nb_samples, out_sample, 1);
//緩衝區的大小
int size = av_samples_get_buffer_size(NULL, out_channer_nb, frame->nb_samples,AV_SAMPLE_FMT_S16, 1);
jbyteArray audio_sample_array = env->NewByteArray(size);
env->SetByteArrayRegion(audio_sample_array, 0, size, (const jbyte *) out_buffer);
env->CallVoidMethod(instance, audio_write, audio_sample_array, size);
env->DeleteLocalRef(audio_sample_array);
//fwrite(out_buffer, 1, out_buffer_size,fp_pcm);
}
av_free_packet(packet);
}
fclose(fp_pcm);
av_frame_free(&frame);
av_free(out_buffer);
swr_free(&swrContext);
avcodec_close(codecCtx);
avformat_close_input(&pFormatCtx);
}
}
這樣手機就能播放出來