Android 部分源碼分析
Android部分的初始化和視頻部分基本相同。 這裏簡單看一下。
- 在SDLActivity中調用了
SDL.setupJNI()
。 SDL.setupJNI()
中SDLAudioManager.nativeSetupJNI()
開始對JNI方法進行初始化。- 在
SDL_android.c
中,nativeSetupJNI
初始化JNI回調java方法的指針。
/* Audio initialization -- called before SDL_main() to initialize JNI bindings */ JNIEXPORT void JNICALL SDL_JAVA_AUDIO_INTERFACE(nativeSetupJNI)(JNIEnv* mEnv, jclass cls) { __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "AUDIO nativeSetupJNI()"); Android_JNI_SetupThread(); mAudioManagerClass = (jclass)((*mEnv)->NewGlobalRef(mEnv, cls)); midAudioOpen = (*mEnv)->GetStaticMethodID(mEnv, mAudioManagerClass, "audioOpen", "(IIII)[I"); midAudioWriteByteBuffer = (*mEnv)->GetStaticMethodID(mEnv, mAudioManagerClass, "audioWriteByteBuffer", "([B)V"); midAudioWriteShortBuffer = (*mEnv)->GetStaticMethodID(mEnv, mAudioManagerClass, "audioWriteShortBuffer", "([S)V"); midAudioWriteFloatBuffer = (*mEnv)->GetStaticMethodID(mEnv, mAudioManagerClass, "audioWriteFloatBuffer", "([F)V"); midAudioClose = (*mEnv)->GetStaticMethodID(mEnv, mAudioManagerClass, "audioClose", "()V"); midCaptureOpen = (*mEnv)->GetStaticMethodID(mEnv, mAudioManagerClass, "captureOpen", "(IIII)[I"); midCaptureReadByteBuffer = (*mEnv)->GetStaticMethodID(mEnv, mAudioManagerClass, "captureReadByteBuffer", "([BZ)I"); midCaptureReadShortBuffer = (*mEnv)->GetStaticMethodID(mEnv, mAudioManagerClass, "captureReadShortBuffer", "([SZ)I"); midCaptureReadFloatBuffer = (*mEnv)->GetStaticMethodID(mEnv, mAudioManagerClass, "captureReadFloatBuffer", "([FZ)I"); midCaptureClose = (*mEnv)->GetStaticMethodID(mEnv, mAudioManagerClass, "captureClose", "()V"); if (!midAudioOpen || !midAudioWriteByteBuffer || !midAudioWriteShortBuffer || !midAudioWriteFloatBuffer || !midAudioClose || !midCaptureOpen || !midCaptureReadByteBuffer || !midCaptureReadShortBuffer || !midCaptureReadFloatBuffer || !midCaptureClose) { __android_log_print(ANDROID_LOG_WARN, "SDL", "Missing some Java callbacks, do you have the latest version of SDLAudioManager.java?"); } checkJNIReady(); }
SDL流程
SDL初始化
SDL_Init(): 初始化SDL。
SDL_OpenAudio(): 打開音頻播放器。
SDL_PauseAudio(): 開始播放。
SDL循環渲染數據
調用callback
,將正確的數據喂入
初始化SDL_AudioInit
在視頻初始化的過程,我們就看到了。SDL_Init方法,傳入SDL_INIT_AUDIO
標誌位,就會走到SDL_AudioInit
方法,對音頻系統進行初始化。
SDL_AudioInit.png
SDL_AudioInit
方法比較簡單,就是將JNI的方法指針給audio.impl。同時設置變量的標誌位。
static int ANDROIDAUDIO_Init(SDL_AudioDriverImpl * impl) { /* Set the function pointers */ impl->OpenDevice = ANDROIDAUDIO_OpenDevice; impl->PlayDevice = ANDROIDAUDIO_PlayDevice; impl->GetDeviceBuf = ANDROIDAUDIO_GetDeviceBuf; impl->CloseDevice = ANDROIDAUDIO_CloseDevice; impl->CaptureFromDevice = ANDROIDAUDIO_CaptureFromDevice; impl->FlushCapture = ANDROIDAUDIO_FlushCapture; /* and the capabilities */ impl->HasCaptureSupport = SDL_TRUE; impl->OnlyHasDefaultOutputDevice = 1; impl->OnlyHasDefaultCaptureDevice = 1; return 1; /* this audio target is available. */ }
在上面Android方法的初始化中,可以看到這些JNI回調java 的方法的實現,都在SDLAudioManager
裏面。
打開音頻播放器SDL_OpenAudio
SDL_OpenAudio.png
- 方法簽名
extern DECLSPEC int SDLCALL SDL_OpenAudio(SDL_AudioSpec * desired, SDL_AudioSpec * obtained);
我們可以看到,SDL_OpenAudio需要傳入兩個參數,一個是我們想要的音頻格式。一個是最後實際的音頻格式。
這裏的SDL_AudioSpec
,是SDL中記錄音頻格式的結構體。
typedef struct SDL_AudioSpec { int freq; /**< DSP frequency -- samples per second */ SDL_AudioFormat format; /**< Audio data format */ Uint8 channels; /**< Number of channels: 1 mono, 2 stereo */ Uint8 silence; /**< Audio buffer silence value (calculated) */ Uint16 samples; /**< Audio buffer size in sample FRAMES (total samples divided by channel count) */ Uint16 padding; /**< Necessary for some compile environments */ Uint32 size; /**< Audio buffer size in bytes (calculated) */ SDL_AudioCallback callback; /**< Callback that feeds the audio device (NULL to use SDL_QueueAudio()). */ void *userdata; /**< Userdata passed to callback (ignored for NULL callbacks). */ } SDL_AudioSpec;
對照函數調用圖。
- 對結果的音頻格式參數,先進行初步的初始化。
- 分配
SDL_AudioDevice
,並初始化 - 對音頻的狀態進行初始化
//目前是非關閉 SDL_AtomicSet(&device->shutdown, 0); /* just in case. */ //暫停 SDL_AtomicSet(&device->paused, 1); //可用狀態 SDL_AtomicSet(&device->enabled, 1);
- 調用
current_audio.impl.OpenDevice
,開啓打開音頻設備。 這個方法最終會調用到SDLAudioManager的open方法。
public static int[] audioOpen(int sampleRate, int audioFormat, int desiredChannels, int desiredFrames) { return open(false, sampleRate, audioFormat, desiredChannels, desiredFrames); } protected static int[] open(boolean isCapture, int sampleRate, int audioFormat, int desiredChannels, int desiredFrames) { int channelConfig; int sampleSize; int frameSize; Log.v(TAG, "Opening " + (isCapture ? "capture" : "playback") + ", requested " + desiredFrames + " frames of " + desiredChannels + " channel " + getAudioFormatString(audioFormat) + " audio at " + sampleRate + " Hz"); /* On older devices let's use known good settings */ if (Build.VERSION.SDK_INT < 21) { if (desiredChannels > 2) { desiredChannels = 2; } if (sampleRate < 8000) { sampleRate = 8000; } else if (sampleRate > 48000) { sampleRate = 48000; } } if (audioFormat == AudioFormat.ENCODING_PCM_FLOAT) { int minSDKVersion = (isCapture ? 23 : 21); if (Build.VERSION.SDK_INT < minSDKVersion) { audioFormat = AudioFormat.ENCODING_PCM_16BIT; } } switch (audioFormat) { case AudioFormat.ENCODING_PCM_8BIT: sampleSize = 1; break; case AudioFormat.ENCODING_PCM_16BIT: sampleSize = 2; break; case AudioFormat.ENCODING_PCM_FLOAT: sampleSize = 4; break; default: Log.v(TAG, "Requested format " + audioFormat + ", getting ENCODING_PCM_16BIT"); audioFormat = AudioFormat.ENCODING_PCM_16BIT; sampleSize = 2; break; } if (isCapture) { switch (desiredChannels) { case 1: channelConfig = AudioFormat.CHANNEL_IN_MONO; break; case 2: channelConfig = AudioFormat.CHANNEL_IN_STEREO; break; default: Log.v(TAG, "Requested " + desiredChannels + " channels, getting stereo"); desiredChannels = 2; channelConfig = AudioFormat.CHANNEL_IN_STEREO; break; } } else { switch (desiredChannels) { case 1: channelConfig = AudioFormat.CHANNEL_OUT_MONO; break; case 2: channelConfig = AudioFormat.CHANNEL_OUT_STEREO; break; case 3: channelConfig = AudioFormat.CHANNEL_OUT_STEREO | AudioFormat.CHANNEL_OUT_FRONT_CENTER; break; case 4: channelConfig = AudioFormat.CHANNEL_OUT_QUAD; break; case 5: channelConfig = AudioFormat.CHANNEL_OUT_QUAD | AudioFormat.CHANNEL_OUT_FRONT_CENTER; break; case 6: channelConfig = AudioFormat.CHANNEL_OUT_5POINT1; break; case 7: channelConfig = AudioFormat.CHANNEL_OUT_5POINT1 | AudioFormat.CHANNEL_OUT_BACK_CENTER; break; case 8: if (Build.VERSION.SDK_INT >= 23) { channelConfig = AudioFormat.CHANNEL_OUT_7POINT1_SURROUND; } else { Log.v(TAG, "Requested " + desiredChannels + " channels, getting 5.1 surround"); desiredChannels = 6; channelConfig = AudioFormat.CHANNEL_OUT_5POINT1; } break; default: Log.v(TAG, "Requested " + desiredChannels + " channels, getting stereo"); desiredChannels = 2; channelConfig = AudioFormat.CHANNEL_OUT_STEREO; break; } /* Log.v(TAG, "Speaker configuration (and order of channels):"); if ((channelConfig & 0x00000004) != 0) { Log.v(TAG, " CHANNEL_OUT_FRONT_LEFT"); } if ((channelConfig & 0x00000008) != 0) { Log.v(TAG, " CHANNEL_OUT_FRONT_RIGHT"); } if ((channelConfig & 0x00000010) != 0) { Log.v(TAG, " CHANNEL_OUT_FRONT_CENTER"); } if ((channelConfig & 0x00000020) != 0) { Log.v(TAG, " CHANNEL_OUT_LOW_FREQUENCY"); } if ((channelConfig & 0x00000040) != 0) { Log.v(TAG, " CHANNEL_OUT_BACK_LEFT"); } if ((channelConfig & 0x00000080) != 0) { Log.v(TAG, " CHANNEL_OUT_BACK_RIGHT"); } if ((channelConfig & 0x00000100) != 0) { Log.v(TAG, " CHANNEL_OUT_FRONT_LEFT_OF_CENTER"); } if ((channelConfig & 0x00000200) != 0) { Log.v(TAG, " CHANNEL_OUT_FRONT_RIGHT_OF_CENTER"); } if ((channelConfig & 0x00000400) != 0) { Log.v(TAG, " CHANNEL_OUT_BACK_CENTER"); } if ((channelConfig & 0x00000800) != 0) { Log.v(TAG, " CHANNEL_OUT_SIDE_LEFT"); } if ((channelConfig & 0x00001000) != 0) { Log.v(TAG, " CHANNEL_OUT_SIDE_RIGHT"); } */ } frameSize = (sampleSize * desiredChannels); // Let the user pick a larger buffer if they really want -- but ye // gods they probably shouldn't, the minimums are horrifyingly high // latency already int minBufferSize; if (isCapture) { minBufferSize = AudioRecord.getMinBufferSize(sampleRate, channelConfig, audioFormat); } else { minBufferSize = AudioTrack.getMinBufferSize(sampleRate, channelConfig, audioFormat); } desiredFrames = Math.max(desiredFrames, (minBufferSize + frameSize - 1) / frameSize); int[] results = new int[4]; if (isCapture) { if (mAudioRecord == null) { mAudioRecord = new AudioRecord(MediaRecorder.AudioSource.DEFAULT, sampleRate, channelConfig, audioFormat, desiredFrames * frameSize); // see notes about AudioTrack state in audioOpen(), above. Probably also applies here. if (mAudioRecord.getState() != AudioRecord.STATE_INITIALIZED) { Log.e(TAG, "Failed during initialization of AudioRecord"); mAudioRecord.release(); mAudioRecord = null; return null; } mAudioRecord.startRecording(); } results[0] = mAudioRecord.getSampleRate(); results[1] = mAudioRecord.getAudioFormat(); results[2] = mAudioRecord.getChannelCount(); results[3] = desiredFrames; } else { if (mAudioTrack == null) { mAudioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, sampleRate, channelConfig, audioFormat, desiredFrames * frameSize, AudioTrack.MODE_STREAM); // Instantiating AudioTrack can "succeed" without an exception and the track may still be invalid // Ref: https://android.googlesource.com/platform/frameworks/base/+/refs/heads/master/media/java/android/media/AudioTrack.java // Ref: http://developer.android.com/reference/android/media/AudioTrack.html#getState() if (mAudioTrack.getState() != AudioTrack.STATE_INITIALIZED) { /* Try again, with safer values */ Log.e(TAG, "Failed during initialization of Audio Track"); mAudioTrack.release(); mAudioTrack = null; return null; } mAudioTrack.play(); } results[0] = mAudioTrack.getSampleRate(); results[1] = mAudioTrack.getAudioFormat(); results[2] = mAudioTrack.getChannelCount(); results[3] = desiredFrames; } Log.v(TAG, "Opening " + (isCapture ? "capture" : "playback") + ", got " + results[3] + " frames of " + results[2] + " channel " + getAudioFormatString(results[1]) + " audio at " + results[0] + " Hz"); return results; }
這個方法,對應SDL傳遞過來的參數。初始化播放使用時對應使用的AudioTrack
。並將最後AudioTrack
配置的後的參數,返回給SDL的desireSpec
。
- 接着使用返回的
audioSpec
和當前的進行對比,重新複製,並且如果發生了改變,則重新創建SDL_AudioStream
。
if (build_stream) { if (iscapture) { device->stream = SDL_NewAudioStream(device->spec.format, device->spec.channels, device->spec.freq, obtained->format, obtained->channels, obtained->freq); } else { device->stream = SDL_NewAudioStream(obtained->format, obtained->channels, obtained->freq, device->spec.format, device->spec.channels, device->spec.freq); } if (!device->stream) { close_audio_device(device); return 0; } }
結構體SDL_AudioStream
struct _SDL_AudioStream { SDL_AudioCVT cvt_before_resampling; SDL_AudioCVT cvt_after_resampling; SDL_DataQueue *queue; SDL_bool first_run; Uint8 *staging_buffer; int staging_buffer_size; int staging_buffer_filled; Uint8 *work_buffer_base; /* maybe unaligned pointer from SDL_realloc(). */ int work_buffer_len; int src_sample_frame_size; SDL_AudioFormat src_format; Uint8 src_channels; int src_rate; int dst_sample_frame_size; SDL_AudioFormat dst_format; Uint8 dst_channels; int dst_rate; double rate_incr; Uint8 pre_resample_channels; int packetlen; int resampler_padding_samples; float *resampler_padding; void *resampler_state; SDL_ResampleAudioStreamFunc resampler_func; SDL_ResetAudioStreamResamplerFunc reset_resampler_func; SDL_CleanupAudioStreamResamplerFunc cleanup_resampler_func; };
這個結構體。保存src和dst 的對應的參數,並通過保存的CVT方法,可以進行方便的轉換。
- 設置callback
如果我們不能設置
callback
(音頻數據的回調)的話,SDL會默認給設置一個數據隊列的管理。 因爲通常,我們不會直接播放 PCM的數據,所以都會自己設置callback,在callback當中進行音頻數據的格式轉換和數據設置。
if (device->spec.callback == NULL) { /* use buffer queueing? */ /* pool a few packets to start. Enough for two callbacks. */ device->buffer_queue = SDL_NewDataQueue(SDL_AUDIOBUFFERQUEUE_PACKETLEN, obtained->size * 2); if (!device->buffer_queue) { close_audio_device(device); SDL_SetError("Couldn't create audio buffer queue"); return 0; } device->callbackspec.callback = iscapture ? SDL_BufferQueueFillCallback : SDL_BufferQueueDrainCallback; device->callbackspec.userdata = device; }
- 最後通過一些配置,然後開啓
SDL_RunAudio線程
。(因爲是播放,如果是錄製,就走另外一個線程SDL_CaptureAudio
)
device->thread = SDL_CreateThreadInternal(iscapture ? SDL_CaptureAudio : SDL_RunAudio, threadname, stacksize, device);
音頻線程SDL_RunAudio
音頻線程SDL_RunAudio.png
- 設置線程的優先級
SDL_SetThreadPriority(SDL_THREAD_PRIORITY_TIME_CRITICAL)
音頻的線程優先級必須是高。 - 判斷是否關閉了device 如果關閉了,就推出循環,否則進入循環。
SDL_AtomicGet(&device->shutdown)
可以看到SDL這裏的音頻播放的幾個參數shutdown
,pause
,enable
都是用了原子性的變量參數,保持其原子性和一致性。
- 確定數據的buff大小。
if (!device->stream && SDL_AtomicGet(&device->enabled)) { SDL_assert(data_len == device->spec.size); data = current_audio.impl.GetDeviceBuf(device); } else { /* if the device isn't enabled, we still write to the work_buffer, so the app's callback will fire with a regular frequency, in case they depend on that for timing or progress. They can use hotplug now to know if the device failed. Streaming playback uses work_buffer, too. */ data = NULL; } if (data == NULL) { data = device->work_buffer; }
如果沒有轉換流而且有設備的話,就去取設備的許可的buf。這個變量在打開設備的時候,進行初始化。值爲 samples*channels 不是的話,就用我們初始化時,傳入的大小。作爲buf.
- 判斷是否callback數據
SDL_LockMutex(device->mixer_lock); if (SDL_AtomicGet(&device->paused)) { SDL_memset(data, device->spec.silence, data_len); } else { callback(udata, data, data_len); } SDL_UnlockMutex(device->mixer_lock);
如果是暫停的情況下,就是簡單設置數據,就結束了。 如果不是暫停的話,就會進入callback(callback中,我們對音頻數據進行讀取,解碼和設置)
- 播放
if (device->stream) { /* Stream available audio to device, converting/resampling. */ /* if this fails...oh well. We'll play silence here. */ SDL_AudioStreamPut(device->stream, data, data_len); while (SDL_AudioStreamAvailable(device->stream) >= ((int) device->spec.size)) { int got; data = SDL_AtomicGet(&device->enabled) ? current_audio.impl.GetDeviceBuf(device) : NULL; got = SDL_AudioStreamGet(device->stream, data ? data : device->work_buffer, device->spec.size); SDL_assert((got < 0) || (got == device->spec.size)); if (data == NULL) { /* device is having issues... */ const Uint32 delay = ((device->spec.samples * 1000) / device->spec.freq); SDL_Delay(delay); /* wait for as long as this buffer would have played. Maybe device recovers later? */ } else { if (got != device->spec.size) { SDL_memset(data, device->spec.silence, device->spec.size); } current_audio.impl.PlayDevice(device); current_audio.impl.WaitDevice(device); } } } else if (data == device->work_buffer) { /* nothing to do; pause like we queued a buffer to play. */ const Uint32 delay = ((device->spec.samples * 1000) / device->spec.freq); SDL_Delay(delay); } else { /* writing directly to the device. */ /* queue this buffer and wait for it to finish playing. */ current_audio.impl.PlayDevice(device); current_audio.impl.WaitDevice(device); }
最後就是進行播放。如果需要轉換的話,就會先進行轉換,再播放。轉換失敗的話,就不會播放聲音。
最後是通過current_audio.impl.PlayDevice(device)
方法播放
PlayDevice.png
該方法,實際上是調用了Android_JNI_WriteAudioBuffer(SDL_android.c)
方法。
因爲是在子線程中,所以需要先通過Android_JNI_GetEnv
,來調用
int status = (*mJavaVM)->AttachCurrentThread(mJavaVM, &env, NULL);
將當前的線程和JVM進行綁定,纔可以調用JNI方法。
然後最後調用的是SDLAudioManager
中的對應的 audioWriteXXXBuffer方法。使用AudioTrack
,將數據進行write
(實際上就是播放)
開始或者暫停音頻播放器SDL_PauseAudio
void SDL_PauseAudioDevice(SDL_AudioDeviceID devid, int pause_on) { SDL_AudioDevice *device = get_audio_device(devid); if (device) { current_audio.impl.LockDevice(device); SDL_AtomicSet(&device->paused, pause_on ? 1 : 0); current_audio.impl.UnlockDevice(device); } }
通過上面對RunAudio線程的分析,我們知道其是改變device->paused
標誌位。來回調callback
。
callback
我們來關注一下我們如何進行callback
的操作
- 傳遞自己的callback
//通過desired_spec 的callback來傳遞我們自己的callback wanted_spec.callback = audio_callback; if (SDL_OpenAudio(&wanted_spec, &spec) < 0) { ALOGE("SDL_OpenAudio: %s \n", SDL_GetError()); return -1; }
- 定義callback
void audio_callback(void *userdata, Uint8 *stream, int len) { AVCodecContext *aCodecCtx = (AVCodecContext *) userdata; int len1, audio_size; static uint8_t audio_buf[(MAX_AUDIO_FRAME_SIZE * 3) / 2]; static unsigned int audio_buf_size = 0; static unsigned int audio_buf_index = 0; // 這裏把得到的數據給重置了 SDL_memset(stream, 0, len); ALOGI("audio_callback len=%d \n", len); //向設備發送長度爲len的數據 while (len > 0) { //緩衝區中無數據 if (audio_buf_index >= audio_buf_size) { //從packet中解碼數據 audio_size = audio_decode_frame(aCodecCtx, audio_buf, audio_buf_size); //ALOGI("audio_decode_frame finish audio_size=%d \n", audio_size); if (audio_size < 0) //沒有解碼到數據或者出錯,填充0 { audio_buf_size = 1024; memset(audio_buf, 0, audio_buf_size); } else { audio_buf_size = audio_size; } audio_buf_index = 0; } len1 = audio_buf_size - audio_buf_index; if (len1 > len) len1 = len; //這種方式是可以直接把數據複製過去 memcpy(stream, (uint8_t *)audio_buf + audio_buf_index, len1); //通過SDL_MixAudio方法,可以控制音量,如果直接使用memcpy是無法控制音量的 // SDL_MixAudio(stream, audio_buf + audio_buf_index, len1, SDL_MIX_MAXVOLUME); //SDL_MixAudioFormat() len -= len1; stream += len1; audio_buf_index += len1; } }
關閉
- 關閉
/** This method is called by SDL using JNI. */ public static void audioClose() { if (mAudioTrack != null) { mAudioTrack.stop(); mAudioTrack.release(); mAudioTrack = null; } }
最後關閉音頻,就是將其stop
和release