diff --git a/frameworks/js/napi/audio_capturer/src/audio_capturer_napi.cpp b/frameworks/js/napi/audio_capturer/src/audio_capturer_napi.cpp index 7d2809d67355cc7b43d491928db367ece0594027..764e56e1cce61df16a0f12af09c4e988faf235ef 100644 --- a/frameworks/js/napi/audio_capturer/src/audio_capturer_napi.cpp +++ b/frameworks/js/napi/audio_capturer/src/audio_capturer_napi.cpp @@ -52,6 +52,7 @@ namespace { constexpr int PARAM2 = 2; constexpr int TYPE_COMMUNICATION = 7; + constexpr int TYPE_PLAYBACK_CAPTURE = 2; constexpr int TYPE_VOICE_RECOGNITION = 1; constexpr int TYPE_MIC = 0; constexpr int TYPE_INVALID = -1; @@ -204,6 +205,9 @@ napi_value AudioCapturerNapi::Construct(napi_env env, napi_callback_info info) capturerOptions.capturerInfo.sourceType = sCapturerOptions_->capturerInfo.sourceType; capturerOptions.capturerInfo.capturerFlags = sCapturerOptions_->capturerInfo.capturerFlags; + capturerOptions.playbackCaptureConfig.filterOptions = + sCapturerOptions_->playbackCaptureConfig.filterOptions; + std::shared_ptr abilityContext = GetAbilityContext(env); std::string cacheDir = ""; if (abilityContext != nullptr) { @@ -369,6 +373,8 @@ void AudioCapturerNapi::GetCapturerAsyncCallbackComplete(napi_env env, napi_stat capturerOptions->streamInfo.channels = asyncContext->capturerOptions.streamInfo.channels; capturerOptions->capturerInfo.sourceType = asyncContext->capturerOptions.capturerInfo.sourceType; capturerOptions->capturerInfo.capturerFlags = asyncContext->capturerOptions.capturerInfo.capturerFlags; + capturerOptions->playbackCaptureConfig.filterOptions = + asyncContext->capturerOptions.playbackCaptureConfig.filterOptions; valueParam = CreateAudioCapturerWrapper(env, capturerOptions); asyncContext->status = AudioCapturerNapi::isConstructSuccess_; @@ -1336,6 +1342,49 @@ napi_value AudioCapturerNapi::GetState(napi_env env, napi_callback_info info) return jsResult; } +bool AudioCapturerNapi::ParseCaptureFilterOptionsVector(napi_env env, napi_value root, + std::vector &filterOptions) +{ + uint32_t arrayLen = 0; + napi_get_array_length(env, root, &arrayLen); + + if (arrayLen == 0) { + filterOptions = {}; + HiLog::Info(LABEL, "ParseCaptureFilterOptions get empty filterOptions"); + return true; + } + + for (size_t i = 0; i < (size_t)arrayLen; i++) { + napi_value element; + napi_get_element(env, root, i, &element); + CaptureFilterOptions option = {}; + napi_value tempValue = nullptr; + int32_t intValue = {0}; + + if (napi_get_named_property(env, element, "usage", &tempValue) == napi_ok ) { + napi_get_value_int32(env, tempValue, &intValue); + option.usage = static_cast(intValue); + } + + filterOptions.push_back(option); + } + + return true; +} + +bool AudioCapturerNapi::ParsePlaybackCaptureConfig(napi_env env, napi_value root, + AudioPlaybackCaptureConfig* captureConfig) +{ + napi_value res = nullptr; + + if (napi_get_named_property(env, root, "filterOptions", &res) == napi_ok) { + return ParseCaptureFilterOptionsVector(env, res, captureConfig->filterOptions); + } else { + HiLog::Info(LABEL, "ParsePlaybackCaptureConfig without filterOptions"); + return true; + } +} + bool AudioCapturerNapi::ParseCapturerOptions(napi_env env, napi_value root, AudioCapturerOptions *opts) { napi_value res = nullptr; @@ -1353,6 +1402,14 @@ bool AudioCapturerNapi::ParseCapturerOptions(napi_env env, napi_value root, Audi result = ParseCapturerInfo(env, res, &(opts->capturerInfo)); } + if (result == false) { + return result; + } + + if (napi_get_named_property(env, root, "playbackCaptureConfig", &res) == napi_ok) { + result = ParsePlaybackCaptureConfig(env, res, &(opts->playbackCaptureConfig)); + } + return result; } @@ -1367,6 +1424,7 @@ bool AudioCapturerNapi::ParseCapturerInfo(napi_env env, napi_value root, AudioCa case TYPE_INVALID: case TYPE_MIC: case TYPE_VOICE_RECOGNITION: + case TYPE_PLAYBACK_CAPTURE: case TYPE_COMMUNICATION: capturerInfo->sourceType = static_cast(intValue); break; diff --git a/frameworks/js/napi/audio_renderer/src/audio_renderer_napi.cpp b/frameworks/js/napi/audio_renderer/src/audio_renderer_napi.cpp index 64d5d54fe22b3ff9fb9c8c3da518c1dda161d4d9..d9f9574769c41fa361d6accbc6f58b9571b82fd8 100644 --- a/frameworks/js/napi/audio_renderer/src/audio_renderer_napi.cpp +++ b/frameworks/js/napi/audio_renderer/src/audio_renderer_napi.cpp @@ -48,6 +48,7 @@ napi_ref AudioRendererNapi::interruptForceType_ = nullptr; napi_ref AudioRendererNapi::audioState_ = nullptr; napi_ref AudioRendererNapi::sampleFormat_ = nullptr; napi_ref AudioRendererNapi::audioEffectMode_ = nullptr; +napi_ref AudioRendererNapi::audioPrivacyType_ = nullptr; mutex AudioRendererNapi::createMutex_; int32_t AudioRendererNapi::isConstructSuccess_ = SUCCESS; @@ -375,6 +376,36 @@ napi_value AudioRendererNapi::CreateAudioEffectModeObject(napi_env env) return result; } +napi_value AudioRendererNapi::CreateAudioPrivacyTypeObject(napi_env env) +{ + napi_value result = nullptr; + napi_status status; + std::string propName; + + status = napi_create_object(env, &result); + if (status == napi_ok) { + for (auto &iter: audioPrivacyTypeMap) { + propName = iter.first; + status = AddNamedProperty(env, result, propName, iter.second); + if (status != napi_ok) { + HiLog::Error(LABEL, "Failed to add named prop in CreateAudioPrivacyTypeObject!"); + break; + } + propName.clear(); + } + if (status == napi_ok) { + status = napi_create_reference(env, result, REFERENCE_CREATION_COUNT, &audioPrivacyType_); + if (status == napi_ok) { + return result; + } + } + } + HiLog::Error(LABEL, "CreateAudioPrivacyTypeObject is Failed!"); + napi_get_undefined(env, &result); + + return result; +} + static void SetDeviceDescriptors(const napi_env& env, napi_value &valueParam, const DeviceInfo &deviceInfo) { SetValueInt32(env, "deviceRole", static_cast(deviceInfo.deviceRole), valueParam); @@ -453,6 +484,7 @@ napi_value AudioRendererNapi::Init(napi_env env, napi_value exports) DECLARE_NAPI_PROPERTY("AudioState", CreateAudioStateObject(env)), DECLARE_NAPI_PROPERTY("AudioSampleFormat", CreateAudioSampleFormatObject(env)), DECLARE_NAPI_PROPERTY("AudioEffectMode", CreateAudioEffectModeObject(env)), + DECLARE_NAPI_PROPERTY("AudioPrivacyType", CreateAudioPrivacyTypeObject(env)), }; status = napi_define_class(env, AUDIO_RENDERER_NAPI_CLASS_NAME.c_str(), NAPI_AUTO_LENGTH, Construct, nullptr, @@ -529,6 +561,7 @@ napi_value AudioRendererNapi::Construct(napi_env env, napi_callback_info info) rendererOptions.rendererInfo.contentType = sRendererOptions_->rendererInfo.contentType; rendererOptions.rendererInfo.streamUsage = sRendererOptions_->rendererInfo.streamUsage; rendererOptions.rendererInfo.rendererFlags = sRendererOptions_->rendererInfo.rendererFlags; + rendererOptions.privacyType = sRendererOptions_->privacyType; std::shared_ptr abilityContext = GetAbilityContext(env); std::string cacheDir = ""; @@ -1019,6 +1052,7 @@ void AudioRendererNapi::GetRendererAsyncCallbackComplete(napi_env env, napi_stat rendererOptions->rendererInfo.contentType = asyncContext->rendererOptions.rendererInfo.contentType; rendererOptions->rendererInfo.streamUsage = asyncContext->rendererOptions.rendererInfo.streamUsage; rendererOptions->rendererInfo.rendererFlags = asyncContext->rendererOptions.rendererInfo.rendererFlags; + rendererOptions->privacyType = asyncContext->rendererOptions.privacyType; valueParam = CreateAudioRendererWrapper(env, rendererOptions); asyncContext->status = AudioRendererNapi::isConstructSuccess_; @@ -2539,6 +2573,12 @@ bool AudioRendererNapi::ParseRendererOptions(napi_env env, napi_value root, Audi ParseStreamInfo(env, res, &(opts->streamInfo)); } + if (napi_get_named_property(env, root, "privacyType", &res) == napi_ok) { + int32_t intValue = {0}; + napi_get_value_int32(env, res, &intValue); + opts->privacyType = static_cast(intValue); + } + return true; } diff --git a/frameworks/native/audioadapter/include/audio_service_adapter.h b/frameworks/native/audioadapter/include/audio_service_adapter.h index 8dc273d6f4431245026dc0e43ef5155ddf503651..6484874d4971a66ddc5dd68e633237ccd01df56b 100644 --- a/frameworks/native/audioadapter/include/audio_service_adapter.h +++ b/frameworks/native/audioadapter/include/audio_service_adapter.h @@ -38,6 +38,8 @@ public: virtual void OnSessionRemoved(const uint32_t sessionID) = 0; + virtual void OnPlaybackCapturerStop() = 0; + virtual ~AudioServiceAdapterCallback() {} }; diff --git a/frameworks/native/audioadapter/src/pulse_audio_service_adapter_impl.cpp b/frameworks/native/audioadapter/src/pulse_audio_service_adapter_impl.cpp index 0d35ca5aa4118227a2b943fc9d2a9a3d0faef36b..c3545dca6d0452058882d262bc2955321254bd3e 100644 --- a/frameworks/native/audioadapter/src/pulse_audio_service_adapter_impl.cpp +++ b/frameworks/native/audioadapter/src/pulse_audio_service_adapter_impl.cpp @@ -1054,6 +1054,7 @@ void PulseAudioServiceAdapterImpl::ProcessSourceOutputEvent(pa_context *c, pa_su uint32_t sessionID = sourceIndexSessionIDMap[idx]; AUDIO_ERR_LOG("[ProcessSourceOutputEvent] sessionID: %{public}d removed", sessionID); g_audioServiceAdapterCallback->OnSessionRemoved(sessionID); + g_audioServiceAdapterCallback->OnPlaybackCapturerStop(); } } diff --git a/frameworks/native/audiocapturer/include/audio_capturer_private.h b/frameworks/native/audiocapturer/include/audio_capturer_private.h index 9b0bdc83a7b9abb4dc3f95de4dfa226b22b105a8..b9a57418b1ee6e03a0dbb03004dcbb28760fa7b1 100644 --- a/frameworks/native/audiocapturer/include/audio_capturer_private.h +++ b/frameworks/native/audiocapturer/include/audio_capturer_private.h @@ -65,7 +65,9 @@ public: AudioCapturerPrivate(AudioStreamType audioStreamType, const AppInfo &appInfo); virtual ~AudioCapturerPrivate(); - bool isChannelChange_ = false; + bool isChannelChange_ = false; + void SetCapturerState(bool state); + private: std::shared_ptr audioStreamCallback_ = nullptr; std::shared_ptr audioInterruptCallback_ = nullptr; diff --git a/frameworks/native/audiocapturer/src/audio_capturer.cpp b/frameworks/native/audiocapturer/src/audio_capturer.cpp index 1fc9f92c5d2a226607690fd7e02931b3a1b7ea3e..61fad3ca316fe15f64ceb13be49987df9f3cda64 100644 --- a/frameworks/native/audiocapturer/src/audio_capturer.cpp +++ b/frameworks/native/audiocapturer/src/audio_capturer.cpp @@ -99,6 +99,11 @@ std::unique_ptr AudioCapturer::Create(const AudioCapturerOptions capturer->SetApplicationCachePath(cachePath); } + if (sourceType == SOURCE_TYPE_PLAYBACK_CAPTURE) { + capturer->SetCapturerState(true); + (void)AudioPolicyManager::GetInstance().SetPlaybackCapturerFilterInfos( + capturerOptions.playbackCaptureConfig.filterOptions); + } capturer->capturerInfo_.sourceType = sourceType; capturer->capturerInfo_.capturerFlags = capturerOptions.capturerInfo.capturerFlags; if (capturer->SetParams(params) != SUCCESS) { @@ -107,6 +112,7 @@ std::unique_ptr AudioCapturer::Create(const AudioCapturerOptions if (isChange) { capturer->isChannelChange_ = true; } + return capturer; } @@ -138,6 +144,11 @@ AudioCapturerPrivate::AudioCapturerPrivate(AudioStreamType audioStreamType, cons } } +void AudioCapturerPrivate::SetCapturerState(bool state) +{ + audioStream_->SetInnerCapturerState(state); +} + int32_t AudioCapturerPrivate::GetFrameCount(uint32_t &frameCount) const { return audioStream_->GetFrameCount(frameCount); diff --git a/frameworks/native/audiocapturer/test/unittest/capturer_test/src/audio_capturer_unit_test.cpp b/frameworks/native/audiocapturer/test/unittest/capturer_test/src/audio_capturer_unit_test.cpp index 117e6e44b038b978ff189f231eee3e24ead951a5..cf3a7ac2287fea3acd351933225b84135d361a06 100644 --- a/frameworks/native/audiocapturer/test/unittest/capturer_test/src/audio_capturer_unit_test.cpp +++ b/frameworks/native/audiocapturer/test/unittest/capturer_test/src/audio_capturer_unit_test.cpp @@ -33,6 +33,7 @@ namespace { const string AUDIO_CAPTURE_FILE2 = "/data/audiocapturetest_nonblocking.pcm"; const string AUDIO_TIME_STABILITY_TEST_FILE = "/data/audiocapture_getaudiotime_stability_test.pcm"; const string AUDIO_FLUSH_STABILITY_TEST_FILE = "/data/audiocapture_flush_stability_test.pcm"; + const string AUDIO_PLAYBACK_CAPTURER_TEST_FILE = "/data/audiocapturer_playbackcapturer_test.pcm"; const int32_t READ_BUFFERS_COUNT = 128; const int32_t VALUE_NEGATIVE = -1; const int32_t VALUE_ZERO = 0; @@ -547,6 +548,68 @@ HWTEST(AudioCapturerUnitTest, Audio_Capturer_Create_020, TestSize.Level0) ASSERT_EQ(nullptr, audioCapturer); } +/** +* @tc.name : Test Create API via legal input. +* @tc.number: Audio_Capturer_Create_021 +* @tc.desc : Test Create interface with AudioCapturerOptions below. +* Returns audioCapturer instance, if create is successful. +* capturerOptions.streamInfo.samplingRate = SAMPLE_RATE_48000; +* capturerOptions.streamInfo.encoding = ENCODING_PCM; +* capturerOptions.streamInfo.format = SAMPLE_S32LE; +* capturerOptions.streamInfo.channels = STEREO; +* capturerOptions.capturerInfo.sourceType = SOURCE_TYPE_PLAYBACK_CAPTURE; +* capturerOptions.capturerInfo.capturerFlags = CAPTURER_FLAG; +*/ +HWTEST(AudioCapturerUnitTest, Audio_Capturer_Create_021, TestSize.Level0) +{ + AudioCapturerOptions capturerOptions; + capturerOptions.streamInfo.samplingRate = AudioSamplingRate::SAMPLE_RATE_48000; + capturerOptions.streamInfo.encoding = AudioEncodingType::ENCODING_PCM; + capturerOptions.streamInfo.format = AudioSampleFormat::SAMPLE_S32LE; + capturerOptions.streamInfo.channels = AudioChannel::STEREO; + capturerOptions.capturerInfo.sourceType = SourceType::SOURCE_TYPE_PLAYBACK_CAPTURE; + capturerOptions.capturerInfo.capturerFlags = CAPTURER_FLAG; + + vector filterOptions = {}; + CaptureFilterOptions filterOption; + filterOption.usage = StreamUsage::STREAM_USAGE_MEDIA; + filterOptions.push_back(filterOption); + filterOption.usage = StreamUsage::STREAM_USAGE_VOICE_COMMUNICATION; + filterOptions.push_back(filterOption); + capturerOptions.playbackCaptureConfig.filterOptions = filterOptions; + + unique_ptr audioCapturer = AudioCapturer::Create(capturerOptions); + ASSERT_NE(nullptr, audioCapturer); + audioCapturer->Release(); +} + +/** +* @tc.name : Test Create API via legal input. +* @tc.number: Audio_Capturer_Create_022 +* @tc.desc : Test Create interface with AudioCapturerOptions below. +* Returns audioCapturer instance, if create is successful. +* capturerOptions.streamInfo.samplingRate = SAMPLE_RATE_48000; +* capturerOptions.streamInfo.encoding = ENCODING_PCM; +* capturerOptions.streamInfo.format = SAMPLE_S32LE; +* capturerOptions.streamInfo.channels = STEREO; +* capturerOptions.capturerInfo.sourceType = SOURCE_TYPE_PLAYBACK_CAPTURE; +* capturerOptions.capturerInfo.capturerFlags = CAPTURER_FLAG; +*/ +HWTEST(AudioCapturerUnitTest, Audio_Capturer_Create_022, TestSize.Level0) +{ + AudioCapturerOptions capturerOptions; + capturerOptions.streamInfo.samplingRate = AudioSamplingRate::SAMPLE_RATE_48000; + capturerOptions.streamInfo.encoding = AudioEncodingType::ENCODING_PCM; + capturerOptions.streamInfo.format = AudioSampleFormat::SAMPLE_S32LE; + capturerOptions.streamInfo.channels = AudioChannel::STEREO; + capturerOptions.capturerInfo.sourceType = SourceType::SOURCE_TYPE_PLAYBACK_CAPTURE; + capturerOptions.capturerInfo.capturerFlags = CAPTURER_FLAG; + + unique_ptr audioCapturer = AudioCapturer::Create(capturerOptions); + ASSERT_NE(nullptr, audioCapturer); + audioCapturer->Release(); +} + /** * @tc.name : Test SetParams API via legal input * @tc.number: Audio_Capturer_SetParams_001 @@ -1251,6 +1314,30 @@ HWTEST(AudioCapturerUnitTest, Audio_Capturer_Start_005, TestSize.Level1) audioCapturer->Release(); } +/** +* @tc.name : Test Start API via legal state, CAPTURER_PREPARED. +* @tc.number: Audio_Capturer_Start_006 +* @tc.desc : Test Start interface. Returns true if start is successful. +*/ +HWTEST(AudioCapturerUnitTest, Audio_Capturer_Start_006, TestSize.Level1) +{ + AudioCapturerOptions capturerOptions; + capturerOptions.streamInfo.samplingRate = AudioSamplingRate::SAMPLE_RATE_48000; + capturerOptions.streamInfo.encoding = AudioEncodingType::ENCODING_PCM; + capturerOptions.streamInfo.format = AudioSampleFormat::SAMPLE_S32LE; + capturerOptions.streamInfo.channels = AudioChannel::STEREO; + capturerOptions.capturerInfo.sourceType = SourceType::SOURCE_TYPE_PLAYBACK_CAPTURE; + capturerOptions.capturerInfo.capturerFlags = CAPTURER_FLAG; + + unique_ptr audioCapturer = AudioCapturer::Create(capturerOptions); + ASSERT_NE(nullptr, audioCapturer); + + bool isStarted = audioCapturer->Start(); + EXPECT_EQ(true, isStarted); + + audioCapturer->Release(); +} + /** * @tc.name : Test Read API via isBlockingRead = true. * @tc.number: Audio_Capturer_Read_001 @@ -1516,6 +1603,60 @@ HWTEST(AudioCapturerUnitTest, Audio_Capturer_Read_007, TestSize.Level1) free(buffer); } +/** +* @tc.name : Test Read API via isBlockingRead = true. +* @tc.number: Audio_Capturer_Read_008 +* @tc.desc : Test Read interface. Returns number of bytes read, if the read is successful. +*/ +HWTEST(AudioCapturerUnitTest, Audio_Capturer_Read_008, TestSize.Level1) +{ + int32_t ret = -1; + bool isBlockingRead = true; + AudioCapturerOptions capturerOptions; + capturerOptions.streamInfo.samplingRate = AudioSamplingRate::SAMPLE_RATE_48000; + capturerOptions.streamInfo.encoding = AudioEncodingType::ENCODING_PCM; + capturerOptions.streamInfo.format = AudioSampleFormat::SAMPLE_S32LE; + capturerOptions.streamInfo.channels = AudioChannel::STEREO; + capturerOptions.capturerInfo.sourceType = SourceType::SOURCE_TYPE_PLAYBACK_CAPTURE; + capturerOptions.capturerInfo.capturerFlags = CAPTURER_FLAG; + + unique_ptr audioCapturer = AudioCapturer::Create(capturerOptions); + ASSERT_NE(nullptr, audioCapturer); + + bool isStarted = audioCapturer->Start(); + EXPECT_EQ(true, isStarted); + + size_t bufferLen; + ret = audioCapturer->GetBufferSize(bufferLen); + EXPECT_EQ(SUCCESS, ret); + + uint8_t *buffer = (uint8_t *) malloc(bufferLen); + ASSERT_NE(nullptr, buffer); + FILE *capFile = fopen(AUDIO_PLAYBACK_CAPTURER_TEST_FILE.c_str(), "wb"); + ASSERT_NE(nullptr, capFile); + + size_t size = 1; + int32_t bytesRead = 0; + int32_t numBuffersToCapture = READ_BUFFERS_COUNT; + + while (numBuffersToCapture) { + bytesRead = audioCapturer->Read(*buffer, bufferLen, isBlockingRead); + if (bytesRead <= 0) { + break; + } else if (bytesRead > 0) { + fwrite(buffer, size, bytesRead, capFile); + numBuffersToCapture--; + } + } + + audioCapturer->Flush(); + audioCapturer->Stop(); + audioCapturer->Release(); + + free(buffer); + fclose(capFile); +} + /** * @tc.name : Test GetAudioTime API via legal input. * @tc.number: Audio_Capturer_GetAudioTime_001 @@ -1955,6 +2096,49 @@ HWTEST(AudioCapturerUnitTest, Audio_Capturer_Stop_005, TestSize.Level1) audioCapturer->Release(); } +/** +* @tc.name : Test Stop API. +* @tc.number: Audio_Capturer_Stop_006 +* @tc.desc : Test Stop interface. Returns true, if the stop is successful. +*/ +HWTEST(AudioCapturerUnitTest, Audio_Capturer_Stop_006, TestSize.Level1) +{ + int32_t ret = -1; + bool isBlockingRead = true; + AudioCapturerOptions capturerOptions; + capturerOptions.streamInfo.samplingRate = AudioSamplingRate::SAMPLE_RATE_48000; + capturerOptions.streamInfo.encoding = AudioEncodingType::ENCODING_PCM; + capturerOptions.streamInfo.format = AudioSampleFormat::SAMPLE_S32LE; + capturerOptions.streamInfo.channels = AudioChannel::STEREO; + capturerOptions.capturerInfo.sourceType = SourceType::SOURCE_TYPE_PLAYBACK_CAPTURE; + capturerOptions.capturerInfo.capturerFlags = CAPTURER_FLAG; + + unique_ptr audioCapturer = AudioCapturer::Create(capturerOptions); + ASSERT_NE(nullptr, audioCapturer); + + bool isStarted = audioCapturer->Start(); + EXPECT_EQ(true, isStarted); + + size_t bufferLen; + ret = audioCapturer->GetBufferSize(bufferLen); + EXPECT_EQ(SUCCESS, ret); + + uint8_t *buffer = (uint8_t *) malloc(bufferLen); + ASSERT_NE(nullptr, buffer); + + int32_t bytesRead = audioCapturer->Read(*buffer, bufferLen, isBlockingRead); + EXPECT_GE(bytesRead, VALUE_ZERO); + + audioCapturer->Flush(); + + bool isStopped = audioCapturer->Stop(); + EXPECT_EQ(true, isStopped); + + audioCapturer->Release(); + + free(buffer); +} + /** * @tc.name : Test Release API. * @tc.number: Audio_Capturer_Release_001 @@ -2069,6 +2253,48 @@ HWTEST(AudioCapturerUnitTest, Audio_Capturer_Release_005, TestSize.Level1) EXPECT_EQ(true, isReleased); } +/** +* @tc.name : Test Release API. +* @tc.number: Audio_Capturer_Release_006 +* @tc.desc : Test Release interface. Returns true, if the release is successful. +*/ +HWTEST(AudioCapturerUnitTest, Audio_Capturer_Release_006, TestSize.Level1) +{ + int32_t ret = -1; + bool isBlockingRead = true; + AudioCapturerOptions capturerOptions; + capturerOptions.streamInfo.samplingRate = AudioSamplingRate::SAMPLE_RATE_48000; + capturerOptions.streamInfo.encoding = AudioEncodingType::ENCODING_PCM; + capturerOptions.streamInfo.format = AudioSampleFormat::SAMPLE_S32LE; + capturerOptions.streamInfo.channels = AudioChannel::STEREO; + capturerOptions.capturerInfo.sourceType = SourceType::SOURCE_TYPE_PLAYBACK_CAPTURE; + capturerOptions.capturerInfo.capturerFlags = CAPTURER_FLAG; + + unique_ptr audioCapturer = AudioCapturer::Create(capturerOptions); + ASSERT_NE(nullptr, audioCapturer); + + bool isStarted = audioCapturer->Start(); + EXPECT_EQ(true, isStarted); + + size_t bufferLen; + ret = audioCapturer->GetBufferSize(bufferLen); + EXPECT_EQ(SUCCESS, ret); + + uint8_t *buffer = (uint8_t *) malloc(bufferLen); + ASSERT_NE(nullptr, buffer); + + int32_t bytesRead = audioCapturer->Read(*buffer, bufferLen, isBlockingRead); + EXPECT_GE(bytesRead, VALUE_ZERO); + + audioCapturer->Flush(); + audioCapturer->Stop(); + + bool isReleased = audioCapturer->Release(); + EXPECT_EQ(true, isReleased); + + free(buffer); +} + /** * @tc.name : Test GetStatus API. * @tc.number: Audio_Capturer_GetStatus_001 diff --git a/frameworks/native/audiopolicy/include/audio_policy_manager.h b/frameworks/native/audiopolicy/include/audio_policy_manager.h index 6aaf5e02f1444bca18484924b006db42fa0f920a..40203af49cf88fda5383b3b6c86c99b0af07e57a 100644 --- a/frameworks/native/audiopolicy/include/audio_policy_manager.h +++ b/frameworks/native/audiopolicy/include/audio_policy_manager.h @@ -225,6 +225,9 @@ public: int32_t GetMaxRendererInstances(); int32_t QueryEffectSceneMode(SupportedEffectConfig &supportedEffectConfig); + + int32_t SetPlaybackCapturerFilterInfos(std::vector filterOptions); + private: AudioPolicyManager() { diff --git a/frameworks/native/audiorenderer/include/audio_renderer_private.h b/frameworks/native/audiorenderer/include/audio_renderer_private.h index 3a8b7f93994e08ed4589bfcc8e366b3a8130a2e8..bfbdd621febde08332b0366302f8182eac8c3347 100644 --- a/frameworks/native/audiorenderer/include/audio_renderer_private.h +++ b/frameworks/native/audiorenderer/include/audio_renderer_private.h @@ -30,6 +30,7 @@ class AudioRendererPrivate : public AudioRenderer { public: int32_t GetFrameCount(uint32_t &frameCount) const override; int32_t GetLatency(uint64_t &latency) const override; + void SetAudioPrivacyType(AudioPrivacyType privacyType) override; int32_t SetParams(const AudioRendererParams params) override; int32_t GetParams(AudioRendererParams ¶ms) const override; int32_t GetRendererInfo(AudioRendererInfo &rendererInfo) const override; diff --git a/frameworks/native/audiorenderer/src/audio_renderer.cpp b/frameworks/native/audiorenderer/src/audio_renderer.cpp index bf8ce9d2da8707feacde6c6dd48e3df3cbe20edb..4bb27ce928781c0aca03d2d51fda7396778d6f7e 100644 --- a/frameworks/native/audiorenderer/src/audio_renderer.cpp +++ b/frameworks/native/audiorenderer/src/audio_renderer.cpp @@ -150,6 +150,9 @@ std::unique_ptr AudioRenderer::Create(const std::string cachePath audioRenderer->rendererInfo_.streamUsage = streamUsage; audioRenderer->rendererInfo_.rendererFlags = rendererFlags; + AudioPrivacyType privacyType = rendererOptions.privacyType; + audioRenderer->SetAudioPrivacyType(privacyType); + AudioRendererParams params; params.sampleFormat = rendererOptions.streamInfo.format; params.sampleRate = rendererOptions.streamInfo.samplingRate; @@ -246,6 +249,11 @@ int32_t AudioRendererPrivate::GetLatency(uint64_t &latency) const return audioStream_->GetLatency(latency); } +void AudioRendererPrivate::SetAudioPrivacyType(AudioPrivacyType privacyType) +{ + audioStream_->SetPrivacyType(privacyType); +} + int32_t AudioRendererPrivate::SetParams(const AudioRendererParams params) { Trace trace("AudioRenderer::SetParams"); diff --git a/frameworks/native/audiostream/include/audio_stream.h b/frameworks/native/audiostream/include/audio_stream.h index 78bf4e12cb0edc4fd43df2b1f1a73954fb317963..a311a7a8377637470def36698ee24ebba221d306 100644 --- a/frameworks/native/audiostream/include/audio_stream.h +++ b/frameworks/native/audiostream/include/audio_stream.h @@ -57,6 +57,9 @@ public: AudioRendererRate GetRenderRate(); int32_t SetStreamCallback(const std::shared_ptr &callback); + void SetInnerCapturerState(bool isInnerCapturer); + void SetPrivacyType(AudioPrivacyType privacyType); + // callback mode api int32_t SetRenderMode(AudioRenderMode renderMode); AudioRenderMode GetRenderMode(); diff --git a/frameworks/native/playbackcapturer/BUILD.gn b/frameworks/native/playbackcapturer/BUILD.gn new file mode 100644 index 0000000000000000000000000000000000000000..f5465863e8473db5dd78b7451493dddcbefe7f78 --- /dev/null +++ b/frameworks/native/playbackcapturer/BUILD.gn @@ -0,0 +1,58 @@ +# Copyright (c) 2023 Huawei Device Co., Ltd. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import("//build/ohos.gni") +import("../../../audio_ohcore.gni") +import("../../../config.gni") + +pulseaudio_dir = "//third_party/pulseaudio" + +config("playback_capturer_config") { + include_dirs = [ + "../audiostream/include", + "../playbackcapturer/include", + "../../../interfaces/inner_api/native/audiocommon/include", + "../../../interfaces/inner_api/native/audiomanager/include", + "$pulseaudio_dir/src", + "$pulseaudio_dir/confgure/src", + ] + + cflags = [ + "-Wall", + "-Werror", + ] +} + +ohos_shared_library("playback_capturer") { + sanitize = { + cfi = true + debug = false + blocklist = "../../../cfi_blocklist.txt" + } + install_enable = true + + configs = [ ":playback_capturer_config" ] + + sources = [ "src/playback_capturer_manager.cpp" ] + + external_deps = [ + "c_utils:utils", + "hiviewdfx_hilog_native:libhilog", + "ipc:ipc_single", + ] + + version_script = "../../../audio_framework.versionscript" + + part_name = "audio_framework" + subsystem_name = "multimedia" +} diff --git a/frameworks/native/playbackcapturer/include/playback_capturer_adapter.h b/frameworks/native/playbackcapturer/include/playback_capturer_adapter.h new file mode 100644 index 0000000000000000000000000000000000000000..987e1bdf5634f45c63dc64d75c6c25faa538ff42 --- /dev/null +++ b/frameworks/native/playbackcapturer/include/playback_capturer_adapter.h @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef PLAYBACK_CAPTURER_ADAPTER_H +#define PLAYBACK_CAPTURER_ADAPTER_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +bool IsStreamSupportInnerCapturer(int32_t streamUsage); +bool IsPrivacySupportInnerCapturer(int32_t privacyTpe); + +#ifdef __cplusplus +} +#endif +#endif // PLAYBACK_CAPTURER_ADAPTER_H + diff --git a/frameworks/native/playbackcapturer/include/playback_capturer_manager.h b/frameworks/native/playbackcapturer/include/playback_capturer_manager.h new file mode 100644 index 0000000000000000000000000000000000000000..f8aff40de22558219629f00dd9c6ce2f4aa2e088 --- /dev/null +++ b/frameworks/native/playbackcapturer/include/playback_capturer_manager.h @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef PLAYBACK_CAPTURER_MANAGER_H +#define PLAYBACK_CAPTURER_MANAGER_H + +#include +#include +#include +#include +#include +#include +#include + +#include "playback_capturer_adapter.h" + +namespace OHOS { +namespace AudioStandard { + +class PlaybackCapturerManager { +public: + PlaybackCapturerManager(); + ~PlaybackCapturerManager(); + static PlaybackCapturerManager *GetInstance(); + void SetSupportStreamUsage(std::vector usage); + bool IsStreamSupportInnerCapturer(int32_t streamUsage); + bool IsPrivacySupportInnerCapturer(int32_t privacyType); +private: + std::unordered_set supportStreamUsageSet_; +}; + +} // namespace AudioStandard +} // namespace OHOS +#endif // PLAYBACK_CAPTURER_MANAGER_H \ No newline at end of file diff --git a/frameworks/native/playbackcapturer/src/playback_capturer_manager.cpp b/frameworks/native/playbackcapturer/src/playback_capturer_manager.cpp new file mode 100644 index 0000000000000000000000000000000000000000..d2088a5e114825d3182d0cd5f092467931d2ef35 --- /dev/null +++ b/frameworks/native/playbackcapturer/src/playback_capturer_manager.cpp @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "playback_capturer_manager.h" + +#include +#include +#include +#include +#include +#include + +#include "audio_info.h" +#include "audio_log.h" +#include "audio_errors.h" +#include "playback_capturer_adapter.h" + +using namespace OHOS::AudioStandard; + +bool IsStreamSupportInnerCapturer(int32_t streamUsage) +{ + PlaybackCapturerManager *playbackCapturerMgr = PlaybackCapturerManager::GetInstance(); + if (playbackCapturerMgr == nullptr) { + AUDIO_ERR_LOG("IsStreamSupportInnerCapturer return false for null manager."); + return false; + } + return playbackCapturerMgr->IsStreamSupportInnerCapturer(streamUsage); +} + +bool IsPrivacySupportInnerCapturer(int32_t privacyType) +{ + PlaybackCapturerManager *playbackCapturerMgr = PlaybackCapturerManager::GetInstance(); + if (playbackCapturerMgr == nullptr) { + AUDIO_ERR_LOG("IsPrivacySupportInnerCapturer return false for null manager."); + return false; + } + + return playbackCapturerMgr->IsPrivacySupportInnerCapturer(privacyType); +} + +namespace OHOS { +namespace AudioStandard { + +PlaybackCapturerManager::PlaybackCapturerManager() {} + +PlaybackCapturerManager::~PlaybackCapturerManager() {} + +PlaybackCapturerManager* PlaybackCapturerManager::GetInstance() +{ + static PlaybackCapturerManager playbackCapturerMgr; + return &playbackCapturerMgr; +} + +void PlaybackCapturerManager::SetSupportStreamUsage(std::vector usage) +{ + supportStreamUsageSet_.clear(); + if (usage.empty()) { + AUDIO_DEBUG_LOG("Clear support streamUsage"); + return; + } + for (size_t i = 0; i < usage.size(); i++) { + supportStreamUsageSet_.emplace(usage[i]); + } +} + +bool PlaybackCapturerManager::IsStreamSupportInnerCapturer(int32_t streamUsage) +{ + if (supportStreamUsageSet_.empty()) { + return streamUsage == STREAM_USAGE_MEDIA; + } + return supportStreamUsageSet_.find(streamUsage) != supportStreamUsageSet_.end(); +} + +bool PlaybackCapturerManager::IsPrivacySupportInnerCapturer(int32_t privacyType) +{ + return privacyType == PRIVACY_TYPE_PUBLIC; +} + +} // namespace OHOS +} // namespace AudioStandard \ No newline at end of file diff --git a/frameworks/native/pulseaudio/modules/BUILD.gn b/frameworks/native/pulseaudio/modules/BUILD.gn index 15bf6f82f587f3e6366387e6508a14fb93388bd3..71a375a936a37bbebe157eb6321b28b088fcad8d 100644 --- a/frameworks/native/pulseaudio/modules/BUILD.gn +++ b/frameworks/native/pulseaudio/modules/BUILD.gn @@ -9,12 +9,15 @@ # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and -# limitations under the License. +# limitations under the License. import("//build/ohos.gni") group("pa_extend_modules") { deps = [ + "abstract:module-loopback", + "capturer:module-inner-capturer-sink", + "capturer:module-receiver-sink", "cluster:module-cluster-sink", "effect:module-effect-sink", "hdi:module-hdi-sink", diff --git a/frameworks/native/pulseaudio/modules/abstract/BUILD.gn b/frameworks/native/pulseaudio/modules/abstract/BUILD.gn new file mode 100644 index 0000000000000000000000000000000000000000..eac22911f9a68e418599d2f6ada06f7e60690c90 --- /dev/null +++ b/frameworks/native/pulseaudio/modules/abstract/BUILD.gn @@ -0,0 +1,64 @@ +# Copyright (c) 2023 Huawei Device Co., Ltd. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import("//build/ohos.gni") +import("../../../../../config.gni") + +pulseaudio_dir = "//third_party/pulseaudio" +pulseaudio_build_path = "//third_party/pulseaudio/ohosbuild" + +config("abstract_config") { + visibility = [ ":*" ] + + include_dirs = [ + "$pulseaudio_dir/include", + "$pulseaudio_dir/src", + "$pulseaudio_dir", + "$pulseaudio_build_path/src", + "$pulseaudio_build_path/include", + "../../../../../interfaces/inner_api/native/audiocommon/include", + ] + + cflags = [ + "-Wall", + "-Werror", + "-DHAVE_CONFIG_H", + "-D_GNU_SOURCE", + "-D__INCLUDED_FROM_PULSE_AUDIO", + ] +} + +ohos_shared_library("module-loopback") { + sanitize = { + cfi = true + debug = false + blocklist = "../../../../../cfi_blocklist.txt" + } + sources = [ "module_loopback.c" ] + + configs = [ ":abstract_config" ] + + cflags = [ "-DPA_MODULE_NAME=libmodule_loopback_z_so" ] + + deps = [ + "$pulseaudio_build_path/src:pulsecommon", + "$pulseaudio_build_path/src/pulse:pulse", + "$pulseaudio_build_path/src/pulsecore:pulsecore", + "//third_party/bounds_checking_function:libsec_shared", + ] + + external_deps = [ "hiviewdfx_hilog_native:libhilog" ] + + subsystem_name = "multimedia" + part_name = "audio_framework" +} diff --git a/frameworks/native/pulseaudio/modules/abstract/module_loopback.c b/frameworks/native/pulseaudio/modules/abstract/module_loopback.c new file mode 100644 index 0000000000000000000000000000000000000000..8bb1ff0d2e814203f236931e4687637f1472ef2d --- /dev/null +++ b/frameworks/native/pulseaudio/modules/abstract/module_loopback.c @@ -0,0 +1,1539 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// GCC does not warn for unused *static inline* functions, but clang does. +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunused-function" +#endif + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include + +#include + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "audio_log.h" + +PA_MODULE_AUTHOR("OpenHarmony"); +PA_MODULE_DESCRIPTION("Loopback from source to sink"); +PA_MODULE_VERSION(PACKAGE_VERSION); +PA_MODULE_LOAD_ONCE(false); +PA_MODULE_USAGE( + "source= " + "sink= " + "adjust_time= " + "latency_msec= " + "max_latency_msec= " + "fast_adjust_threshold_msec= " + "format= " + "rate= " + "channels= " + "channel_map= " + "sink_input_properties= " + "source_output_properties= " + "source_dont_move= " + "sink_dont_move= " + "remix= "); + +#define DEFAULT_LATENCY_MSEC 200 + +#define MEMBLOCKQ_MAXLENGTH (1024*1024*32) + +#define MIN_DEVICE_LATENCY (2.5*PA_USEC_PER_MSEC) + +#define DEFAULT_ADJUST_TIME_USEC (10*PA_USEC_PER_SEC) + +typedef struct loopback_msg loopback_msg; + +struct userdata { + pa_core *core; + pa_module *module; + + loopback_msg *msg; + + pa_sink_input *sink_input; + pa_source_output *source_output; + + pa_asyncmsgq *asyncmsgq; + pa_memblockq *memblockq; + + pa_rtpoll_item *rtpoll_item_read, *rtpoll_item_write; + + pa_time_event *time_event; + + /* Variables used to calculate the average time between + * subsequent calls of adjust_rates() */ + pa_usec_t adjust_time_stamp; + pa_usec_t real_adjust_time; + pa_usec_t real_adjust_time_sum; + + /* Values from command line configuration */ + pa_usec_t latency; + pa_usec_t max_latency; + pa_usec_t adjust_time; + pa_usec_t fast_adjust_threshold; + + /* Latency boundaries and current values */ + pa_usec_t min_source_latency; + pa_usec_t max_source_latency; + pa_usec_t min_sink_latency; + pa_usec_t max_sink_latency; + pa_usec_t configured_sink_latency; + pa_usec_t configured_source_latency; + int64_t source_latency_offset; + int64_t sink_latency_offset; + pa_usec_t minimum_latency; + + /* lower latency limit found by underruns */ + pa_usec_t underrun_latency_limit; + + /* Various counters */ + uint32_t iteration_counter; + uint32_t underrun_counter; + uint32_t adjust_counter; + + bool fixed_alsa_source; + bool source_sink_changed; + + /* Used for sink input and source output snapshots */ + struct { + int64_t send_counter; + int64_t source_latency; + pa_usec_t source_timestamp; + + int64_t recv_counter; + size_t loopback_memblockq_length; + int64_t sink_latency; + pa_usec_t sink_timestamp; + } latency_snapshot; + + /* Input thread variable */ + int64_t send_counter; + + /* Output thread variables */ + struct { + int64_t recv_counter; + pa_usec_t effective_source_latency; + + /* Copied from main thread */ + pa_usec_t minimum_latency; + + /* Various booleans */ + bool in_pop; + bool pop_called; + bool pop_adjust; + bool first_pop_done; + bool push_called; + } output_thread_info; +}; + +struct loopback_msg { + pa_msgobject parent; + struct userdata *userdata; +}; + +PA_DEFINE_PRIVATE_CLASS(loopback_msg, pa_msgobject); +#define LOOPBACK_MSG(o) (loopback_msg_cast(o)) + +static const char* const valid_modargs[] = { + "source", + "sink", + "adjust_time", + "latency_msec", + "max_latency_msec", + "fast_adjust_threshold_msec", + "format", + "rate", + "channels", + "channel_map", + "sink_input_properties", + "source_output_properties", + "source_dont_move", + "sink_dont_move", + "remix", + NULL, +}; + +enum { + SINK_INPUT_MESSAGE_POST = PA_SINK_INPUT_MESSAGE_MAX, + SINK_INPUT_MESSAGE_REWIND, + SINK_INPUT_MESSAGE_LATENCY_SNAPSHOT, + SINK_INPUT_MESSAGE_SOURCE_CHANGED, + SINK_INPUT_MESSAGE_SET_EFFECTIVE_SOURCE_LATENCY, + SINK_INPUT_MESSAGE_UPDATE_MIN_LATENCY, + SINK_INPUT_MESSAGE_FAST_ADJUST, +}; + +enum { + SOURCE_OUTPUT_MESSAGE_LATENCY_SNAPSHOT = PA_SOURCE_OUTPUT_MESSAGE_MAX, +}; + +enum { + LOOPBACK_MESSAGE_SOURCE_LATENCY_RANGE_CHANGED, + LOOPBACK_MESSAGE_SINK_LATENCY_RANGE_CHANGED, + LOOPBACK_MESSAGE_UNDERRUN, +}; + +static void enable_adjust_timer(struct userdata *u, bool enable); + +/* Called from main context */ +static void teardown(struct userdata *u) { + pa_assert(u); + pa_assert_ctl_context(); + + u->adjust_time = 0; + enable_adjust_timer(u, false); + + /* Handling the asyncmsgq between the source output and the sink input + * requires some care. When the source output is unlinked, nothing needs + * to be done for the asyncmsgq, because the source output is the sending + * end. But when the sink input is unlinked, we should ensure that the + * asyncmsgq is emptied, because the messages in the queue hold references + * to the sink input. Also, we need to ensure that new messages won't be + * written to the queue after we have emptied it. + * + * Emptying the queue can be done in the state_change() callback of the + * sink input, when the new state is "unlinked". + * + * Preventing new messages from being written to the queue can be achieved + * by unlinking the source output before unlinking the sink input. There + * are no other writers for that queue, so this is sufficient. */ + + if (u->source_output) { + pa_source_output_unlink(u->source_output); + pa_source_output_unref(u->source_output); + u->source_output = NULL; + } + + if (u->sink_input) { + pa_sink_input_unlink(u->sink_input); + pa_sink_input_unref(u->sink_input); + u->sink_input = NULL; + } +} + +/* rate controller, called from main context + * - maximum deviation from base rate is less than 1% + * - can create audible artifacts by changing the rate too quickly + * - exhibits hunting with USB or Bluetooth sources + */ +static uint32_t rate_controller( + uint32_t base_rate, + pa_usec_t adjust_time, + int32_t latency_difference_usec) { + + uint32_t new_rate; + double min_cycles; + + /* Calculate best rate to correct the current latency offset, limit at + * slightly below 1% difference from base_rate */ + min_cycles = (double)abs(latency_difference_usec) / adjust_time / 0.01 + 1; + new_rate = base_rate * (1.0 + (double)latency_difference_usec / min_cycles / adjust_time); + + return new_rate; +} + +/* Called from main thread. + * It has been a matter of discussion how to correctly calculate the minimum + * latency that module-loopback can deliver with a given source and sink. + * The calculation has been placed in a separate function so that the definition + * can easily be changed. The resulting estimate is not very exact because it + * depends on the reported latency ranges. In cases were the lower bounds of + * source and sink latency are not reported correctly (USB) the result will + * be wrong. */ +static void update_minimum_latency(struct userdata *u, pa_sink *sink, bool print_msg) { + + if (u->underrun_latency_limit) + /* If we already detected a real latency limit because of underruns, use it */ + u->minimum_latency = u->underrun_latency_limit; + + else { + /* Calculate latency limit from latency ranges */ + + u->minimum_latency = u->min_sink_latency; + if (u->fixed_alsa_source) + /* If we are using an alsa source with fixed latency, we will get a wakeup when + * one fragment is filled, and then we empty the source buffer, so the source + * latency never grows much beyond one fragment (assuming that the CPU doesn't + * cause a bottleneck). */ + u->minimum_latency += u->core->default_fragment_size_msec * PA_USEC_PER_MSEC; + + else + /* In all other cases the source will deliver new data at latest after one source latency. + * Make sure there is enough data available that the sink can keep on playing until new + * data is pushed. */ + u->minimum_latency += u->min_source_latency; + + /* Multiply by 1.1 as a safety margin for delays that are proportional to the buffer sizes */ + u->minimum_latency *= 1.1; + + /* Add 1.5 ms as a safety margin for delays not related to the buffer sizes */ + u->minimum_latency += 1.5 * PA_USEC_PER_MSEC; + } + + /* Add the latency offsets */ + if (-(u->sink_latency_offset + u->source_latency_offset) <= (int64_t)u->minimum_latency) + u->minimum_latency += u->sink_latency_offset + u->source_latency_offset; + else + u->minimum_latency = 0; + + /* If the sink is valid, send a message to update the minimum latency to + * the output thread, else set the variable directly */ + if (sink) + pa_asyncmsgq_send(sink->asyncmsgq, PA_MSGOBJECT(u->sink_input), SINK_INPUT_MESSAGE_UPDATE_MIN_LATENCY, + NULL, u->minimum_latency, NULL); + else + u->output_thread_info.minimum_latency = u->minimum_latency; + + if (print_msg) { + AUDIO_INFO_LOG("Minimum possible end to end latency: %0.2f ms", (double)u->minimum_latency / PA_USEC_PER_MSEC); + if (u->latency < u->minimum_latency) + AUDIO_WARNING_LOG("Configured latency of %0.2f ms is smaller than minimum latency, using minimum instead", + (double)u->latency / PA_USEC_PER_MSEC); + } +} + +/* Called from main context */ +static void adjust_rates(struct userdata *u) { + size_t buffer; + uint32_t old_rate, base_rate, new_rate, run_hours; + int32_t latency_difference; + pa_usec_t current_buffer_latency, snapshot_delay; + int64_t current_source_sink_latency, current_latency, latency_at_optimum_rate; + pa_usec_t final_latency, now, time_passed; + + pa_assert(u); + pa_assert_ctl_context(); + + /* Runtime and counters since last change of source or sink + * or source/sink latency */ + run_hours = u->iteration_counter * u->real_adjust_time / PA_USEC_PER_SEC / 3600; + u->iteration_counter +=1; + + /* If we are seeing underruns then the latency is too small */ + if (u->underrun_counter > 2) { + pa_usec_t target_latency; + + target_latency = PA_MAX(u->latency, u->minimum_latency) + 5 * PA_USEC_PER_MSEC; + + if (u->max_latency == 0 || target_latency < u->max_latency) { + u->underrun_latency_limit = + PA_CLIP_SUB((int64_t)target_latency, u->sink_latency_offset + u->source_latency_offset); + AUDIO_WARNING_LOG("Too many underruns, increasing latency to %0.2f ms", + (double)target_latency / PA_USEC_PER_MSEC); + } else { + u->underrun_latency_limit = PA_CLIP_SUB((int64_t)u->max_latency, + u->sink_latency_offset + u->source_latency_offset); + AUDIO_WARNING_LOG("Too many underruns, configured maximum latency of %0.2f ms is reached", + (double)u->max_latency / PA_USEC_PER_MSEC); + AUDIO_WARNING_LOG("Consider increasing the max_latency_msec"); + } + + update_minimum_latency(u, u->sink_input->sink, false); + u->underrun_counter = 0; + } + + /* Allow one underrun per hour */ + if (u->iteration_counter * u->real_adjust_time / PA_USEC_PER_SEC / 3600 > run_hours) { + u->underrun_counter = PA_CLIP_SUB(u->underrun_counter, 1u); + AUDIO_INFO_LOG("Underrun counter: %u", u->underrun_counter); + } + + /* Calculate real adjust time if source or sink did not change and if the system has + * not been suspended. If the time between two calls is more than 5% longer than the + * configured adjust time, we assume that the system has been sleeping and skip the + * calculation for this iteration. */ + now = pa_rtclock_now(); + time_passed = now - u->adjust_time_stamp; + if (!u->source_sink_changed && time_passed < u->adjust_time * 1.05) { + u->adjust_counter++; + u->real_adjust_time_sum += time_passed; + u->real_adjust_time = u->real_adjust_time_sum / u->adjust_counter; + } + u->adjust_time_stamp = now; + + /* Rates and latencies */ + old_rate = u->sink_input->sample_spec.rate; + base_rate = u->source_output->sample_spec.rate; + + buffer = u->latency_snapshot.loopback_memblockq_length; + if (u->latency_snapshot.recv_counter <= u->latency_snapshot.send_counter) + buffer += (size_t) (u->latency_snapshot.send_counter - u->latency_snapshot.recv_counter); + else + buffer = PA_CLIP_SUB(buffer, (size_t) (u->latency_snapshot.recv_counter - u->latency_snapshot.send_counter)); + + current_buffer_latency = pa_bytes_to_usec(buffer, &u->sink_input->sample_spec); + snapshot_delay = u->latency_snapshot.source_timestamp - u->latency_snapshot.sink_timestamp; + current_source_sink_latency = u->latency_snapshot.sink_latency + + u->latency_snapshot.source_latency - snapshot_delay; + + /* Current latency */ + current_latency = current_source_sink_latency + current_buffer_latency; + + /* Latency at base rate */ + latency_at_optimum_rate = current_source_sink_latency + current_buffer_latency * old_rate / base_rate; + + final_latency = PA_MAX(u->latency, u->minimum_latency); + latency_difference = (int32_t)(latency_at_optimum_rate - final_latency); + + AUDIO_DEBUG_LOG("Loopback overall latency is %0.2f ms + %0.2f ms + %0.2f ms = %0.2f ms", + (double) u->latency_snapshot.sink_latency / PA_USEC_PER_MSEC, + (double) current_buffer_latency / PA_USEC_PER_MSEC, + (double) u->latency_snapshot.source_latency / PA_USEC_PER_MSEC, + (double) current_latency / PA_USEC_PER_MSEC); + + AUDIO_DEBUG_LOG("Loopback latency at base rate is %0.2f ms", (double)latency_at_optimum_rate / PA_USEC_PER_MSEC); + + /* Drop or insert samples if fast_adjust_threshold_msec was specified and the latency difference is too large. */ + if (u->fast_adjust_threshold > 0 && abs(latency_difference) > u->fast_adjust_threshold) { + AUDIO_DEBUG_LOG ("Latency difference larger than %" PRIu64 " msec, skipping or inserting samples.", + u->fast_adjust_threshold / PA_USEC_PER_MSEC); + + pa_asyncmsgq_send(u->sink_input->sink->asyncmsgq, PA_MSGOBJECT(u->sink_input), + SINK_INPUT_MESSAGE_FAST_ADJUST, NULL, current_source_sink_latency, NULL); + + /* Skip real adjust time calculation on next iteration. */ + u->source_sink_changed = true; + return; + } + + /* Calculate new rate */ + new_rate = rate_controller(base_rate, u->real_adjust_time, latency_difference); + + u->source_sink_changed = false; + + /* Set rate */ + pa_sink_input_set_rate(u->sink_input, new_rate); + AUDIO_DEBUG_LOG("[%s] Updated sampling rate to %lu Hz.", u->sink_input->sink->name, (unsigned long) new_rate); +} + +/* Called from main context */ +static void time_callback(pa_mainloop_api *a, pa_time_event *e, const struct timeval *t, void *userdata) { + struct userdata *u = userdata; + + pa_assert(u); + pa_assert(a); + pa_assert(u->time_event == e); + + /* Restart timer right away */ + pa_core_rttime_restart(u->core, u->time_event, pa_rtclock_now() + u->adjust_time); + + /* Get sink and source latency snapshot */ + pa_asyncmsgq_send(u->sink_input->sink->asyncmsgq, + PA_MSGOBJECT(u->sink_input), SINK_INPUT_MESSAGE_LATENCY_SNAPSHOT, NULL, 0, NULL); + pa_asyncmsgq_send(u->source_output->source->asyncmsgq, + PA_MSGOBJECT(u->source_output), SOURCE_OUTPUT_MESSAGE_LATENCY_SNAPSHOT, NULL, 0, NULL); + + adjust_rates(u); +} + +/* Called from main context + * When source or sink changes, + * give it a third of a second to settle down, then call adjust_rates for the first time */ +static void enable_adjust_timer(struct userdata *u, bool enable) { + if (enable) { + if (!u->adjust_time) + return; + if (u->time_event) + u->core->mainloop->time_free(u->time_event); + + u->time_event = pa_core_rttime_new(u->core, pa_rtclock_now() + 333 * PA_USEC_PER_MSEC, time_callback, u); + } else { + if (!u->time_event) + return; + + u->core->mainloop->time_free(u->time_event); + u->time_event = NULL; + } +} + +/* Called from main context */ +static void update_adjust_timer(struct userdata *u) { + if (u->sink_input->state == PA_SINK_INPUT_CORKED || u->source_output->state == PA_SOURCE_OUTPUT_CORKED) + enable_adjust_timer(u, false); + else + enable_adjust_timer(u, true); +} + +/* Called from main thread + * Calculates minimum and maximum possible latency for source and sink */ +static void update_latency_boundaries(struct userdata *u, pa_source *source, pa_sink *sink) { + const char *s; + + if (source) { + /* Source latencies */ + u->fixed_alsa_source = false; + if (source->flags & PA_SOURCE_DYNAMIC_LATENCY) + pa_source_get_latency_range(source, &u->min_source_latency, &u->max_source_latency); + else { + u->min_source_latency = pa_source_get_fixed_latency(source); + u->max_source_latency = u->min_source_latency; + if ((s = pa_proplist_gets(source->proplist, PA_PROP_DEVICE_API))) { + if (pa_streq(s, "alsa")) + u->fixed_alsa_source = true; + } + } + /* Source offset */ + u->source_latency_offset = source->port_latency_offset; + + /* Latencies below 2.5 ms cause problems, limit source latency if possible */ + if (u->max_source_latency >= MIN_DEVICE_LATENCY) + u->min_source_latency = PA_MAX(u->min_source_latency, MIN_DEVICE_LATENCY); + else + u->min_source_latency = u->max_source_latency; + } + + if (sink) { + /* Sink latencies */ + if (sink->flags & PA_SINK_DYNAMIC_LATENCY) + pa_sink_get_latency_range(sink, &u->min_sink_latency, &u->max_sink_latency); + else { + u->min_sink_latency = pa_sink_get_fixed_latency(sink); + u->max_sink_latency = u->min_sink_latency; + } + /* Sink offset */ + u->sink_latency_offset = sink->port_latency_offset; + + /* Latencies below 2.5 ms cause problems, limit sink latency if possible */ + if (u->max_sink_latency >= MIN_DEVICE_LATENCY) + u->min_sink_latency = PA_MAX(u->min_sink_latency, MIN_DEVICE_LATENCY); + else + u->min_sink_latency = u->max_sink_latency; + } + + update_minimum_latency(u, sink, true); +} + +/* Called from output context + * Sets the memblockq to the configured latency corrected by latency_offset_usec */ +static void memblockq_adjust(struct userdata *u, int64_t latency_offset_usec, bool allow_push) { + size_t current_memblockq_length, requested_memblockq_length, buffer_correction; + int64_t requested_buffer_latency; + pa_usec_t final_latency, requested_sink_latency; + + final_latency = PA_MAX(u->latency, u->output_thread_info.minimum_latency); + + /* If source or sink have some large negative latency offset, we might want to + * hold more than final_latency in the memblockq */ + requested_buffer_latency = (int64_t)final_latency - latency_offset_usec; + + /* Keep at least one sink latency in the queue to make sure that the sink + * never underruns initially */ + requested_sink_latency = pa_sink_get_requested_latency_within_thread(u->sink_input->sink); + if (requested_buffer_latency < (int64_t)requested_sink_latency) + requested_buffer_latency = requested_sink_latency; + + requested_memblockq_length = pa_usec_to_bytes(requested_buffer_latency, &u->sink_input->sample_spec); + current_memblockq_length = pa_memblockq_get_length(u->memblockq); + + if (current_memblockq_length > requested_memblockq_length) { + /* Drop audio from queue */ + buffer_correction = current_memblockq_length - requested_memblockq_length; + AUDIO_INFO_LOG("Dropping %" PRIu64 " usec of audio from queue", + pa_bytes_to_usec(buffer_correction, &u->sink_input->sample_spec)); + pa_memblockq_drop(u->memblockq, buffer_correction); + + } else if (current_memblockq_length < requested_memblockq_length && allow_push) { + /* Add silence to queue */ + buffer_correction = requested_memblockq_length - current_memblockq_length; + AUDIO_INFO_LOG("Adding %" PRIu64 " usec of silence to queue", + pa_bytes_to_usec(buffer_correction, &u->sink_input->sample_spec)); + pa_memblockq_seek(u->memblockq, (int64_t)buffer_correction, PA_SEEK_RELATIVE, true); + } +} + +/* Called from input thread context */ +static void source_output_push_cb(pa_source_output *o, const pa_memchunk *chunk) { + struct userdata *u; + pa_usec_t push_time; + int64_t current_source_latency; + + pa_source_output_assert_ref(o); + pa_source_output_assert_io_context(o); + pa_assert_se(u = o->userdata); + + /* Send current source latency and timestamp with the message */ + push_time = pa_rtclock_now(); + current_source_latency = pa_source_get_latency_within_thread(u->source_output->source, true); + + pa_asyncmsgq_post(u->asyncmsgq, PA_MSGOBJECT(u->sink_input), SINK_INPUT_MESSAGE_POST, + PA_INT_TO_PTR(current_source_latency), push_time, chunk, NULL); + u->send_counter += (int64_t) chunk->length; +} + +/* Called from input thread context */ +static void source_output_process_rewind_cb(pa_source_output *o, size_t nbytes) { + struct userdata *u; + + pa_source_output_assert_ref(o); + pa_source_output_assert_io_context(o); + pa_assert_se(u = o->userdata); + + pa_asyncmsgq_post(u->asyncmsgq, PA_MSGOBJECT(u->sink_input), SINK_INPUT_MESSAGE_REWIND, + NULL, (int64_t) nbytes, NULL, NULL); + u->send_counter -= (int64_t) nbytes; +} + +/* Called from input thread context */ +static int source_output_process_msg_cb(pa_msgobject *obj, int code, void *data, int64_t offset, pa_memchunk *chunk) { + struct userdata *u = PA_SOURCE_OUTPUT(obj)->userdata; + + switch (code) { + + case SOURCE_OUTPUT_MESSAGE_LATENCY_SNAPSHOT: { + size_t length; + + length = pa_memblockq_get_length(u->source_output->thread_info.delay_memblockq); + + u->latency_snapshot.send_counter = u->send_counter; + /* Add content of delay memblockq to the source latency */ + u->latency_snapshot.source_latency = pa_source_get_latency_within_thread(u->source_output->source, true) + + pa_bytes_to_usec(length, &u->source_output->source->sample_spec); + u->latency_snapshot.source_timestamp = pa_rtclock_now(); + + return 0; + } + } + + return pa_source_output_process_msg(obj, code, data, offset, chunk); +} + +/* Called from main thread. + * Get current effective latency of the source. If the source is in use with + * smaller latency than the configured latency, it will continue running with + * the smaller value when the source output is switched to the source. */ +static void update_effective_source_latency(struct userdata *u, pa_source *source, pa_sink *sink) { + pa_usec_t effective_source_latency; + + effective_source_latency = u->configured_source_latency; + + if (source) { + effective_source_latency = pa_source_get_requested_latency(source); + if (effective_source_latency == 0 || effective_source_latency > u->configured_source_latency) + effective_source_latency = u->configured_source_latency; + } + + /* If the sink is valid, send a message to the output thread, else set the variable directly */ + if (sink) + pa_asyncmsgq_send(sink->asyncmsgq, PA_MSGOBJECT(u->sink_input), + SINK_INPUT_MESSAGE_SET_EFFECTIVE_SOURCE_LATENCY, NULL, (int64_t)effective_source_latency, NULL); + else + u->output_thread_info.effective_source_latency = effective_source_latency; +} + +/* Called from main thread. + * Set source output latency to one third of the overall latency if possible. + * The choice of one third is rather arbitrary somewhere between the minimum + * possible latency which would cause a lot of CPU load and half the configured + * latency which would quickly lead to underruns */ +static void set_source_output_latency(struct userdata *u, pa_source *source) { + pa_usec_t latency, requested_latency; + + requested_latency = u->latency / 3; + + /* Normally we try to configure sink and source latency equally. If the + * sink latency cannot match the requested source latency try to set the + * source latency to a smaller value to avoid underruns */ + if (u->min_sink_latency > requested_latency) { + latency = PA_MAX(u->latency, u->minimum_latency); + requested_latency = (latency - u->min_sink_latency) / 2; + } + + latency = PA_CLAMP(requested_latency , u->min_source_latency, u->max_source_latency); + u->configured_source_latency = pa_source_output_set_requested_latency(u->source_output, latency); + if (u->configured_source_latency != requested_latency) + AUDIO_WARNING_LOG("Cannot set requested source latency of %0.2f ms, adjusting to %0.2f ms", + (double)requested_latency / PA_USEC_PER_MSEC, (double)u->configured_source_latency / PA_USEC_PER_MSEC); +} + +/* Called from input thread context */ +static void source_output_attach_cb(pa_source_output *o) { + struct userdata *u; + + pa_source_output_assert_ref(o); + pa_source_output_assert_io_context(o); + pa_assert_se(u = o->userdata); + + u->rtpoll_item_write = pa_rtpoll_item_new_asyncmsgq_write( + o->source->thread_info.rtpoll, + PA_RTPOLL_LATE, + u->asyncmsgq); +} + +/* Called from input thread context */ +static void source_output_detach_cb(pa_source_output *o) { + struct userdata *u; + + pa_source_output_assert_ref(o); + pa_source_output_assert_io_context(o); + pa_assert_se(u = o->userdata); + + if (u->rtpoll_item_write) { + pa_rtpoll_item_free(u->rtpoll_item_write); + u->rtpoll_item_write = NULL; + } +} + +/* Called from main thread */ +static void source_output_kill_cb(pa_source_output *o) { + struct userdata *u; + + pa_source_output_assert_ref(o); + pa_assert_ctl_context(); + pa_assert_se(u = o->userdata); + + teardown(u); + pa_module_unload_request(u->module, true); +} + +/* Called from main thread */ +static bool source_output_may_move_to_cb(pa_source_output *o, pa_source *dest) { + struct userdata *u; + + pa_source_output_assert_ref(o); + pa_assert_ctl_context(); + pa_assert_se(u = o->userdata); + + if (!u->sink_input || !u->sink_input->sink) + return true; + + return dest != u->sink_input->sink->monitor_source; +} + +/* Called from main thread */ +static void source_output_suspend_cb(pa_source_output *o, pa_source_state_t old_state, + pa_suspend_cause_t old_suspend_cause) { + struct userdata *u; + bool suspended; + + pa_source_output_assert_ref(o); + pa_assert_ctl_context(); + pa_assert_se(u = o->userdata); + + /* State has not changed, nothing to do */ + if (old_state == o->source->state) + return; + + suspended = (o->source->state == PA_SOURCE_SUSPENDED); + + /* If the source has been suspended, we need to handle this like + * a source change when the source is resumed */ + if (suspended) { + if (u->sink_input->sink) + pa_asyncmsgq_send(u->sink_input->sink->asyncmsgq, PA_MSGOBJECT(u->sink_input), + SINK_INPUT_MESSAGE_SOURCE_CHANGED, NULL, 0, NULL); + else + u->output_thread_info.push_called = false; + + } else + /* Get effective source latency on unsuspend */ + update_effective_source_latency(u, u->source_output->source, u->sink_input->sink); + + pa_sink_input_cork(u->sink_input, suspended); + + update_adjust_timer(u); +} + +/* Called from input thread context */ +static void update_source_latency_range_cb(pa_source_output *i) { + struct userdata *u; + + pa_source_output_assert_ref(i); + pa_source_output_assert_io_context(i); + pa_assert_se(u = i->userdata); + + /* Source latency may have changed */ + pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(u->msg), + LOOPBACK_MESSAGE_SOURCE_LATENCY_RANGE_CHANGED, NULL, 0, NULL, NULL); +} + +/* Called from output thread context */ +static int sink_input_pop_cb(pa_sink_input *i, size_t nbytes, pa_memchunk *chunk) { + struct userdata *u; + + pa_sink_input_assert_ref(i); + pa_sink_input_assert_io_context(i); + pa_assert_se(u = i->userdata); + pa_assert(chunk); + + /* It seems necessary to handle outstanding push messages here, though it is not clear + * why. Removing this part leads to underruns when low latencies are configured. */ + u->output_thread_info.in_pop = true; + while (pa_asyncmsgq_process_one(u->asyncmsgq) > 0) + ; + u->output_thread_info.in_pop = false; + + /* While pop has not been called, latency adjustments in SINK_INPUT_MESSAGE_POST are + * enabled. Disable them on second pop and enable the final adjustment during the + * next push. The adjustment must be done on the next push, because there is no way + * to retrieve the source latency here. We are waiting for the second pop, because + * the first pop may be called before the sink is actually started. */ + if (!u->output_thread_info.pop_called && u->output_thread_info.first_pop_done) { + u->output_thread_info.pop_adjust = true; + u->output_thread_info.pop_called = true; + } + u->output_thread_info.first_pop_done = true; + + if (pa_memblockq_peek(u->memblockq, chunk) < 0) { + return -1; + } + + chunk->length = PA_MIN(chunk->length, nbytes); + pa_memblockq_drop(u->memblockq, chunk->length); + + /* Adjust the memblockq to ensure that there is + * enough data in the queue to avoid underruns. */ + if (!u->output_thread_info.push_called) + memblockq_adjust(u, 0, true); + + return 0; +} + +/* Called from output thread context */ +static void sink_input_process_rewind_cb(pa_sink_input *i, size_t nbytes) { + struct userdata *u; + + pa_sink_input_assert_ref(i); + pa_sink_input_assert_io_context(i); + pa_assert_se(u = i->userdata); + + pa_memblockq_rewind(u->memblockq, nbytes); +} + +/* Called from output thread context */ +static int sink_input_process_msg_cb(pa_msgobject *obj, int code, void *data, + int64_t offset, pa_memchunk *chunk) { + struct userdata *u = PA_SINK_INPUT(obj)->userdata; + + pa_sink_input_assert_io_context(u->sink_input); + + switch (code) { + + case PA_SINK_INPUT_MESSAGE_GET_LATENCY: { + pa_usec_t *r = data; + + *r = pa_bytes_to_usec(pa_memblockq_get_length(u->memblockq), &u->sink_input->sample_spec); + + /* Fall through, the default handler will add in the extra + * latency added by the resampler */ + break; + } + + case SINK_INPUT_MESSAGE_POST: + + pa_memblockq_push_align(u->memblockq, chunk); + + /* If push has not been called yet, latency adjustments in sink_input_pop_cb() + * are enabled. Disable them on first push and correct the memblockq. If pop + * has not been called yet, wait until the pop_cb() requests the adjustment */ + if (u->output_thread_info.pop_called && (!u->output_thread_info.push_called || + u->output_thread_info.pop_adjust)) { + int64_t time_delta; + + /* This is the source latency at the time push was called */ + time_delta = PA_PTR_TO_INT(data); + /* Add the time between push and post */ + time_delta += pa_rtclock_now() - (pa_usec_t) offset; + /* Add the sink latency */ + time_delta += pa_sink_get_latency_within_thread(u->sink_input->sink, true); + + /* The source latency report includes the audio in the chunk, + * but since we already pushed the chunk to the memblockq, we need + * to subtract the chunk size from the source latency so that it + * won't be counted towards both the memblockq latency and the + * source latency. + * + * Sometimes the alsa source reports way too low latency (might + * be a bug in the alsa source code). This seems to happen when + * there's an overrun. As an attempt to detect overruns, we + * check if the chunk size is larger than the configured source + * latency. If so, we assume that the source should have pushed + * a chunk whose size equals the configured latency, so we + * modify time_delta only by that amount, which makes + * memblockq_adjust() drop more data than it would otherwise. + * This seems to work quite well, but it's possible that the + * next push also contains too much data, and in that case the + * resulting latency will be wrong. */ + if (pa_bytes_to_usec(chunk->length, + &u->sink_input->sample_spec) > u->output_thread_info.effective_source_latency) + time_delta -= (int64_t)u->output_thread_info.effective_source_latency; + else + time_delta -= (int64_t)pa_bytes_to_usec(chunk->length, &u->sink_input->sample_spec); + + /* FIXME: We allow pushing silence here to fix up the latency. This + * might lead to a gap in the stream */ + memblockq_adjust(u, time_delta, true); + + u->output_thread_info.pop_adjust = false; + u->output_thread_info.push_called = true; + } + + /* If pop has not been called yet, make sure the latency does not grow too much. + * Don't push any silence here, because we already have new data in the queue */ + if (!u->output_thread_info.pop_called) + memblockq_adjust(u, 0, false); + + /* Is this the end of an underrun? Then let's start things + * right-away */ + if (u->sink_input->sink->thread_info.state != PA_SINK_SUSPENDED && + u->sink_input->thread_info.underrun_for > 0 && + pa_memblockq_is_readable(u->memblockq)) { + + pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(u->msg), LOOPBACK_MESSAGE_UNDERRUN, + NULL, 0, NULL, NULL); + /* If called from within the pop callback skip the rewind */ + if (!u->output_thread_info.in_pop) { + AUDIO_DEBUG_LOG("Requesting rewind due to end of underrun."); + pa_sink_input_request_rewind(u->sink_input, + (size_t) (u->sink_input->thread_info.underrun_for == + (size_t) -1 ? 0 : u->sink_input->thread_info.underrun_for), + false, true, false); + } + } + + u->output_thread_info.recv_counter += (int64_t) chunk->length; + + return 0; + + case SINK_INPUT_MESSAGE_REWIND: + + /* Do not try to rewind if no data was pushed yet */ + if (u->output_thread_info.push_called) + pa_memblockq_seek(u->memblockq, -offset, PA_SEEK_RELATIVE, true); + + u->output_thread_info.recv_counter -= offset; + + return 0; + + case SINK_INPUT_MESSAGE_LATENCY_SNAPSHOT: { + size_t length; + + length = pa_memblockq_get_length(u->sink_input->thread_info.render_memblockq); + + u->latency_snapshot.recv_counter = u->output_thread_info.recv_counter; + u->latency_snapshot.loopback_memblockq_length = pa_memblockq_get_length(u->memblockq); + /* Add content of render memblockq to sink latency */ + u->latency_snapshot.sink_latency = pa_sink_get_latency_within_thread(u->sink_input->sink, true) + + pa_bytes_to_usec(length, &u->sink_input->sink->sample_spec); + u->latency_snapshot.sink_timestamp = pa_rtclock_now(); + + return 0; + } + + case SINK_INPUT_MESSAGE_SOURCE_CHANGED: + + u->output_thread_info.push_called = false; + + return 0; + + case SINK_INPUT_MESSAGE_SET_EFFECTIVE_SOURCE_LATENCY: + + u->output_thread_info.effective_source_latency = (pa_usec_t)offset; + + return 0; + + case SINK_INPUT_MESSAGE_UPDATE_MIN_LATENCY: + + u->output_thread_info.minimum_latency = (pa_usec_t)offset; + + return 0; + + case SINK_INPUT_MESSAGE_FAST_ADJUST: + + memblockq_adjust(u, offset, true); + + return 0; + } + + return pa_sink_input_process_msg(obj, code, data, offset, chunk); +} +/* Called from main thread. + * Set sink input latency to one third of the overall latency if possible. + * The choice of one third is rather arbitrary somewhere between the minimum + * possible latency which would cause a lot of CPU load and half the configured + * latency which would quickly lead to underruns. */ +static void set_sink_input_latency(struct userdata *u, pa_sink *sink) { + pa_usec_t latency, requested_latency; + + requested_latency = u->latency / 3; + + /* Normally we try to configure sink and source latency equally. If the + * source latency cannot match the requested sink latency try to set the + * sink latency to a smaller value to avoid underruns */ + if (u->min_source_latency > requested_latency) { + latency = PA_MAX(u->latency, u->minimum_latency); + requested_latency = (latency - u->min_source_latency) / 2; + } + + latency = PA_CLAMP(requested_latency , u->min_sink_latency, u->max_sink_latency); + u->configured_sink_latency = pa_sink_input_set_requested_latency(u->sink_input, latency); + if (u->configured_sink_latency != requested_latency) + AUDIO_WARNING_LOG("Cannot set requested sink latency of %0.2f ms, adjusting to %0.2f ms", + (double)requested_latency / PA_USEC_PER_MSEC, (double)u->configured_sink_latency / PA_USEC_PER_MSEC); +} + +/* Called from output thread context */ +static void sink_input_attach_cb(pa_sink_input *i) { + struct userdata *u; + + pa_sink_input_assert_ref(i); + pa_sink_input_assert_io_context(i); + pa_assert_se(u = i->userdata); + + u->rtpoll_item_read = pa_rtpoll_item_new_asyncmsgq_read( + i->sink->thread_info.rtpoll, + PA_RTPOLL_LATE, + u->asyncmsgq); + + pa_memblockq_set_prebuf(u->memblockq, pa_sink_input_get_max_request(i)*2); + pa_memblockq_set_maxrewind(u->memblockq, pa_sink_input_get_max_rewind(i)); +} + +/* Called from output thread context */ +static void sink_input_detach_cb(pa_sink_input *i) { + struct userdata *u; + + pa_sink_input_assert_ref(i); + pa_sink_input_assert_io_context(i); + pa_assert_se(u = i->userdata); + + if (u->rtpoll_item_read) { + pa_rtpoll_item_free(u->rtpoll_item_read); + u->rtpoll_item_read = NULL; + } +} + +/* Called from output thread context */ +static void sink_input_update_max_rewind_cb(pa_sink_input *i, size_t nbytes) { + struct userdata *u; + + pa_sink_input_assert_ref(i); + pa_sink_input_assert_io_context(i); + pa_assert_se(u = i->userdata); + + pa_memblockq_set_maxrewind(u->memblockq, nbytes); +} + +/* Called from output thread context */ +static void sink_input_update_max_request_cb(pa_sink_input *i, size_t nbytes) { + struct userdata *u; + + pa_sink_input_assert_ref(i); + pa_sink_input_assert_io_context(i); + pa_assert_se(u = i->userdata); + + pa_memblockq_set_prebuf(u->memblockq, nbytes*2); + AUDIO_INFO_LOG("Max request changed"); +} + +/* Called from main thread */ +static void sink_input_kill_cb(pa_sink_input *i) { + struct userdata *u; + + pa_sink_input_assert_ref(i); + pa_assert_ctl_context(); + pa_assert_se(u = i->userdata); + + teardown(u); + pa_module_unload_request(u->module, true); +} + +/* Called from the output thread context */ +static void sink_input_state_change_cb(pa_sink_input *i, pa_sink_input_state_t state) { + struct userdata *u; + + pa_sink_input_assert_ref(i); + pa_assert_se(u = i->userdata); + + if (state == PA_SINK_INPUT_UNLINKED) + pa_asyncmsgq_flush(u->asyncmsgq, false); +} + +/* Called from main thread */ +static bool sink_input_may_move_to_cb(pa_sink_input *i, pa_sink *dest) { + struct userdata *u; + + pa_sink_input_assert_ref(i); + pa_assert_ctl_context(); + pa_assert_se(u = i->userdata); + + if (!u->source_output || !u->source_output->source) + return true; + + return dest != u->source_output->source->monitor_of; +} + +/* Called from main thread */ +static void sink_input_suspend_cb(pa_sink_input *i, pa_sink_state_t old_state, pa_suspend_cause_t old_suspend_cause) { + struct userdata *u; + bool suspended; + + pa_sink_input_assert_ref(i); + pa_assert_ctl_context(); + pa_assert_se(u = i->userdata); + + /* State has not changed, nothing to do */ + if (old_state == i->sink->state) + return; + + suspended = (i->sink->state == PA_SINK_SUSPENDED); + + /* If the sink has been suspended, we need to handle this like + * a sink change when the sink is resumed. Because the sink + * is suspended, we can set the variables directly. */ + if (suspended) { + u->output_thread_info.pop_called = false; + u->output_thread_info.first_pop_done = false; + + } else + /* Set effective source latency on unsuspend */ + update_effective_source_latency(u, u->source_output->source, u->sink_input->sink); + + pa_source_output_cork(u->source_output, suspended); + + update_adjust_timer(u); +} + +/* Called from output thread context */ +static void update_sink_latency_range_cb(pa_sink_input *i) { + struct userdata *u; + + pa_sink_input_assert_ref(i); + pa_sink_input_assert_io_context(i); + pa_assert_se(u = i->userdata); + + /* Sink latency may have changed */ + pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(u->msg), + LOOPBACK_MESSAGE_SINK_LATENCY_RANGE_CHANGED, NULL, 0, NULL, NULL); +} + +/* Called from main context */ +static int loopback_process_msg_cb(pa_msgobject *o, int code, void *userdata, int64_t offset, pa_memchunk *chunk) { + struct loopback_msg *msg; + struct userdata *u; + pa_usec_t current_latency; + + pa_assert(o); + pa_assert_ctl_context(); + + msg = LOOPBACK_MSG(o); + pa_assert_se(u = msg->userdata); + + switch (code) { + case LOOPBACK_MESSAGE_SOURCE_LATENCY_RANGE_CHANGED: + update_effective_source_latency(u, u->source_output->source, u->sink_input->sink); + current_latency = pa_source_get_requested_latency(u->source_output->source); + if (current_latency > u->configured_source_latency) { + /* The minimum latency has changed to a value larger than the configured latency, so + * the source latency has been increased. The case that the minimum latency changes + * back to a smaller value is not handled because this never happens with the current + * source implementations. */ + AUDIO_WARNING_LOG("Source minimum latency increased to %0.2f ms", + (double)current_latency / PA_USEC_PER_MSEC); + u->configured_source_latency = current_latency; + update_latency_boundaries(u, u->source_output->source, u->sink_input->sink); + /* We re-start counting when the latency has changed */ + u->iteration_counter = 0; + u->underrun_counter = 0; + } + return 0; + + case LOOPBACK_MESSAGE_SINK_LATENCY_RANGE_CHANGED: + current_latency = pa_sink_get_requested_latency(u->sink_input->sink); + if (current_latency > u->configured_sink_latency) { + /* The minimum latency has changed to a value larger than the configured latency, so + * the sink latency has been increased. The case that the minimum latency changes back + * to a smaller value is not handled because this never happens with the current sink + * implementations. */ + AUDIO_WARNING_LOG("Sink minimum latency increased to %0.2f ms", + (double)current_latency / PA_USEC_PER_MSEC); + u->configured_sink_latency = current_latency; + update_latency_boundaries(u, u->source_output->source, u->sink_input->sink); + /* We re-start counting when the latency has changed */ + u->iteration_counter = 0; + u->underrun_counter = 0; + } + return 0; + + case LOOPBACK_MESSAGE_UNDERRUN: + u->underrun_counter++; + AUDIO_DEBUG_LOG("Underrun detected, counter incremented to %u", u->underrun_counter); + return 0; + } + + return 0; +} + +static pa_hook_result_t sink_port_latency_offset_changed_cb(pa_core *core, pa_sink *sink, struct userdata *u) +{ + if (sink != u->sink_input->sink) + return PA_HOOK_OK; + + u->sink_latency_offset = sink->port_latency_offset; + update_minimum_latency(u, sink, true); + + return PA_HOOK_OK; +} + +static pa_hook_result_t source_port_latency_offset_changed_cb(pa_core *core, pa_source *source, struct userdata *u) +{ + if (source != u->source_output->source) + return PA_HOOK_OK; + + u->source_latency_offset = source->port_latency_offset; + update_minimum_latency(u, u->sink_input->sink, true); + + return PA_HOOK_OK; +} + +int InitFailed(pa_module *m, pa_modargs *ma) +{ + AUDIO_ERR_LOG("Init Loopback failed."); + if (ma) + pa_modargs_free(ma); + + pa__done(m); + + return -1; +} + +int pa__init(pa_module *m) { + pa_modargs *ma = NULL; + struct userdata *u; + pa_sink *sink = NULL; + pa_sink_input_new_data sink_input_data; + pa_source *source = NULL; + pa_source_output_new_data source_output_data; + uint32_t latency_msec; + uint32_t max_latency_msec; + uint32_t fast_adjust_threshold; + pa_sample_spec ss; + pa_channel_map map; + bool format_set = false; + bool rate_set = false; + bool channels_set = false; + pa_memchunk silence; + uint32_t adjust_time_sec; + const char *n; + + pa_assert(m); + + if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { + AUDIO_ERR_LOG("Failed to parse module arguments"); + return InitFailed(m, ma); + } + + n = pa_modargs_get_value(ma, "source", NULL); + if (n && !(source = pa_namereg_get(m->core, n, PA_NAMEREG_SOURCE))) { + AUDIO_ERR_LOG("No such source."); + return InitFailed(m, ma); + } + + n = pa_modargs_get_value(ma, "sink", NULL); + if (n && !(sink = pa_namereg_get(m->core, n, PA_NAMEREG_SINK))) { + AUDIO_ERR_LOG("No such sink."); + return InitFailed(m, ma); + } + + if (source) { + ss = source->sample_spec; + map = source->channel_map; + format_set = true; + rate_set = true; + channels_set = true; + } else if (sink) { + ss = sink->sample_spec; + map = sink->channel_map; + format_set = true; + rate_set = true; + channels_set = true; + } else { + /* FIXME: Dummy stream format, needed because pa_sink_input_new() + * requires valid sample spec and channel map even when all the FIX_* + * stream flags are specified. pa_sink_input_new() should be changed + * to ignore the sample spec and channel map when the FIX_* flags are + * present. */ + ss.format = PA_SAMPLE_U8; + ss.rate = 8000; + ss.channels = 1; + map.channels = 1; + map.map[0] = PA_CHANNEL_POSITION_MONO; + } + + if (pa_modargs_get_sample_spec_and_channel_map(ma, &ss, &map, PA_CHANNEL_MAP_DEFAULT) < 0) { + AUDIO_ERR_LOG("Invalid sample format specification or channel map"); + return InitFailed(m, ma); + } + + if (ss.rate < 4000 || ss.rate > PA_RATE_MAX) { + AUDIO_ERR_LOG("Invalid rate specification, valid range is 4000 Hz to %i Hz", PA_RATE_MAX); + return InitFailed(m, ma); + } + + if (pa_modargs_get_value(ma, "format", NULL)) + format_set = true; + + if (pa_modargs_get_value(ma, "rate", NULL)) + rate_set = true; + + if (pa_modargs_get_value(ma, "channels", NULL) || pa_modargs_get_value(ma, "channel_map", NULL)) + channels_set = true; + + latency_msec = DEFAULT_LATENCY_MSEC; + if (pa_modargs_get_value_u32(ma, "latency_msec", &latency_msec) < 0 || latency_msec < 1 || latency_msec > 30000) { + AUDIO_ERR_LOG("Invalid latency specification"); + return InitFailed(m, ma); + } + + fast_adjust_threshold = 0; + if (pa_modargs_get_value_u32(ma, "fast_adjust_threshold_msec", &fast_adjust_threshold) < 0 || + (fast_adjust_threshold != 0 && fast_adjust_threshold < 100)) { + AUDIO_ERR_LOG("Invalid fast adjust threshold specification"); + return InitFailed(m, ma); + } + + max_latency_msec = 0; + if (pa_modargs_get_value_u32(ma, "max_latency_msec", &max_latency_msec) < 0) { + AUDIO_ERR_LOG("Invalid maximum latency specification"); + return InitFailed(m, ma); + } + + if (max_latency_msec > 0 && max_latency_msec < latency_msec) { + AUDIO_WARNING_LOG("Configured maximum latency is smaller than latency, using latency instead"); + max_latency_msec = latency_msec; + } + + m->userdata = u = pa_xnew0(struct userdata, 1); + u->core = m->core; + u->module = m; + u->latency = (pa_usec_t) latency_msec * PA_USEC_PER_MSEC; + u->max_latency = (pa_usec_t) max_latency_msec * PA_USEC_PER_MSEC; + u->output_thread_info.pop_called = false; + u->output_thread_info.pop_adjust = false; + u->output_thread_info.push_called = false; + u->iteration_counter = 0; + u->underrun_counter = 0; + u->underrun_latency_limit = 0; + u->source_sink_changed = true; + u->real_adjust_time_sum = 0; + u->adjust_counter = 0; + u->fast_adjust_threshold = fast_adjust_threshold * PA_USEC_PER_MSEC; + + adjust_time_sec = DEFAULT_ADJUST_TIME_USEC / PA_USEC_PER_SEC; + if (pa_modargs_get_value_u32(ma, "adjust_time", &adjust_time_sec) < 0) { + AUDIO_ERR_LOG("Failed to parse adjust_time value"); + return InitFailed(m, ma); + } + + if (adjust_time_sec != DEFAULT_ADJUST_TIME_USEC / PA_USEC_PER_SEC) + u->adjust_time = adjust_time_sec * PA_USEC_PER_SEC; + else + u->adjust_time = DEFAULT_ADJUST_TIME_USEC; + + u->real_adjust_time = u->adjust_time; + + pa_source_output_new_data_init(&source_output_data); + source_output_data.driver = __FILE__; + source_output_data.module = m; + if (source) + pa_source_output_new_data_set_source(&source_output_data, source, false, true); + + if (pa_modargs_get_proplist(ma, "source_output_properties", source_output_data.proplist, PA_UPDATE_REPLACE) < 0) { + AUDIO_ERR_LOG("Failed to parse the source_output_properties value."); + pa_source_output_new_data_done(&source_output_data); + return InitFailed(m, ma); + } + + if (!pa_proplist_contains(source_output_data.proplist, PA_PROP_MEDIA_ROLE)) + pa_proplist_sets(source_output_data.proplist, PA_PROP_MEDIA_ROLE, "abstract"); + + pa_source_output_new_data_set_sample_spec(&source_output_data, &ss); + pa_source_output_new_data_set_channel_map(&source_output_data, &map); + source_output_data.flags = PA_SOURCE_OUTPUT_START_CORKED | PA_SOURCE_OUTPUT_DONT_MOVE; + + if (!format_set) + source_output_data.flags |= PA_SOURCE_OUTPUT_FIX_FORMAT; + + if (!rate_set) + source_output_data.flags |= PA_SOURCE_OUTPUT_FIX_RATE; + + if (!channels_set) + source_output_data.flags |= PA_SOURCE_OUTPUT_FIX_CHANNELS; + + AUDIO_ERR_LOG("source_output_data.driver:%{public}s, module:%{pulic}s, source_output addr:%{public}p", + source_output_data.driver, source_output_data.module->name, &u->source_output); + + pa_source_output_new(&u->source_output, m->core, &source_output_data); + AUDIO_INFO_LOG("pa_source_output_new DONE"); + pa_source_output_new_data_done(&source_output_data); + + if (!u->source_output) + return InitFailed(m, ma); + + u->source_output->parent.process_msg = source_output_process_msg_cb; + u->source_output->push = source_output_push_cb; + u->source_output->process_rewind = source_output_process_rewind_cb; + u->source_output->kill = source_output_kill_cb; + u->source_output->attach = source_output_attach_cb; + u->source_output->detach = source_output_detach_cb; + u->source_output->suspend = source_output_suspend_cb; + u->source_output->update_source_latency_range = update_source_latency_range_cb; + u->source_output->update_source_fixed_latency = update_source_latency_range_cb; + u->source_output->userdata = u; + + /* If format, rate or channels were originally unset, they are set now + * after the pa_source_output_new() call. */ + ss = u->source_output->sample_spec; + map = u->source_output->channel_map; + + pa_sink_input_new_data_init(&sink_input_data); + sink_input_data.driver = __FILE__; + sink_input_data.module = m; + + if (sink) + pa_sink_input_new_data_set_sink(&sink_input_data, sink, false, true); + + if (pa_modargs_get_proplist(ma, "sink_input_properties", sink_input_data.proplist, PA_UPDATE_REPLACE) < 0) { + AUDIO_ERR_LOG("Failed to parse the sink_input_properties value."); + pa_sink_input_new_data_done(&sink_input_data); + return InitFailed(m, ma); + } + + if (!pa_proplist_contains(sink_input_data.proplist, PA_PROP_MEDIA_ROLE)) + pa_proplist_sets(sink_input_data.proplist, PA_PROP_MEDIA_ROLE, "abstract"); + + pa_sink_input_new_data_set_sample_spec(&sink_input_data, &ss); + pa_sink_input_new_data_set_channel_map(&sink_input_data, &map); + sink_input_data.flags = PA_SINK_INPUT_VARIABLE_RATE | PA_SINK_INPUT_START_CORKED | PA_SINK_INPUT_DONT_MOVE; + + pa_sink_input_new(&u->sink_input, m->core, &sink_input_data); + pa_sink_input_new_data_done(&sink_input_data); + + if (!u->sink_input) + return InitFailed(m, ma); + + u->sink_input->parent.process_msg = sink_input_process_msg_cb; + u->sink_input->pop = sink_input_pop_cb; + u->sink_input->process_rewind = sink_input_process_rewind_cb; + u->sink_input->kill = sink_input_kill_cb; + u->sink_input->state_change = sink_input_state_change_cb; + u->sink_input->attach = sink_input_attach_cb; + u->sink_input->detach = sink_input_detach_cb; + u->sink_input->update_max_rewind = sink_input_update_max_rewind_cb; + u->sink_input->update_max_request = sink_input_update_max_request_cb; + u->sink_input->suspend = sink_input_suspend_cb; + u->sink_input->update_sink_latency_range = update_sink_latency_range_cb; + u->sink_input->update_sink_fixed_latency = update_sink_latency_range_cb; + u->sink_input->userdata = u; + + update_latency_boundaries(u, u->source_output->source, u->sink_input->sink); + set_sink_input_latency(u, u->sink_input->sink); + set_source_output_latency(u, u->source_output->source); + + pa_sink_input_get_silence(u->sink_input, &silence); + u->memblockq = pa_memblockq_new( + "module-loopback memblockq", + 0, /* idx */ + MEMBLOCKQ_MAXLENGTH, /* maxlength */ + MEMBLOCKQ_MAXLENGTH, /* tlength */ + &ss, /* sample_spec */ + 0, /* prebuf */ + 0, /* minreq */ + 0, /* maxrewind */ + &silence); /* silence frame */ + pa_memblock_unref(silence.memblock); + /* Fill the memblockq with silence */ + pa_memblockq_seek(u->memblockq, pa_usec_to_bytes(u->latency, &u->sink_input->sample_spec), PA_SEEK_RELATIVE, true); + + u->asyncmsgq = pa_asyncmsgq_new(0); + if (!u->asyncmsgq) { + AUDIO_ERR_LOG("pa_asyncmsgq_new() failed."); + return InitFailed(m, ma); + } + + if (!pa_proplist_contains(u->source_output->proplist, PA_PROP_MEDIA_NAME)) + pa_proplist_setf(u->source_output->proplist, PA_PROP_MEDIA_NAME, "Loopback to %s", + pa_strnull(pa_proplist_gets(u->sink_input->sink->proplist, PA_PROP_DEVICE_DESCRIPTION))); + + if (!pa_proplist_contains(u->source_output->proplist, PA_PROP_MEDIA_ICON_NAME) + && (n = pa_proplist_gets(u->sink_input->sink->proplist, PA_PROP_DEVICE_ICON_NAME))) + pa_proplist_sets(u->source_output->proplist, PA_PROP_MEDIA_ICON_NAME, n); + + if (!pa_proplist_contains(u->sink_input->proplist, PA_PROP_MEDIA_NAME)) + pa_proplist_setf(u->sink_input->proplist, PA_PROP_MEDIA_NAME, "Loopback from %s", + pa_strnull(pa_proplist_gets(u->source_output->source->proplist, PA_PROP_DEVICE_DESCRIPTION))); + + if (source && !pa_proplist_contains(u->sink_input->proplist, PA_PROP_MEDIA_ICON_NAME) + && (n = pa_proplist_gets(u->source_output->source->proplist, PA_PROP_DEVICE_ICON_NAME))) + pa_proplist_sets(u->sink_input->proplist, PA_PROP_MEDIA_ICON_NAME, n); + + /* Hooks to track changes of latency offsets */ + pa_module_hook_connect(m, &m->core->hooks[PA_CORE_HOOK_SINK_PORT_LATENCY_OFFSET_CHANGED], + PA_HOOK_NORMAL, (pa_hook_cb_t) sink_port_latency_offset_changed_cb, u); + pa_module_hook_connect(m, &m->core->hooks[PA_CORE_HOOK_SOURCE_PORT_LATENCY_OFFSET_CHANGED], + PA_HOOK_NORMAL, (pa_hook_cb_t) source_port_latency_offset_changed_cb, u); + + /* Setup message handler for main thread */ + u->msg = pa_msgobject_new(loopback_msg); + u->msg->parent.process_msg = loopback_process_msg_cb; + u->msg->userdata = u; + + /* The output thread is not yet running, set effective_source_latency directly */ + update_effective_source_latency(u, u->source_output->source, NULL); + + pa_sink_input_put(u->sink_input); + pa_source_output_put(u->source_output); + + if (u->source_output->source->state != PA_SOURCE_SUSPENDED) + pa_sink_input_cork(u->sink_input, false); + + if (u->sink_input->sink->state != PA_SINK_SUSPENDED) + pa_source_output_cork(u->source_output, false); + + pa_modargs_free(ma); + return 0; +} + +void pa__done(pa_module*m) { + struct userdata *u; + + pa_assert(m); + + if (!(u = m->userdata)) + return; + + teardown(u); + + if (u->memblockq) + pa_memblockq_free(u->memblockq); + + if (u->asyncmsgq) + pa_asyncmsgq_unref(u->asyncmsgq); + + pa_xfree(u); +} \ No newline at end of file diff --git a/frameworks/native/pulseaudio/modules/capturer/BUILD.gn b/frameworks/native/pulseaudio/modules/capturer/BUILD.gn new file mode 100644 index 0000000000000000000000000000000000000000..0862f4efc7f1ad4588cf58ede985e8542648d5b5 --- /dev/null +++ b/frameworks/native/pulseaudio/modules/capturer/BUILD.gn @@ -0,0 +1,97 @@ +# Copyright (c) 2023 Huawei Device Co., Ltd. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import("//build/ohos.gni") +import("../../../../../config.gni") + +pulseaudio_dir = "//third_party/pulseaudio" +pulseaudio_build_path = "//third_party/pulseaudio/ohosbuild" + +config("capturer_config") { + visibility = [ ":*" ] + + include_dirs = [ + "$pulseaudio_dir/include", + "$pulseaudio_dir/src", + "$pulseaudio_dir", + "$pulseaudio_build_path/src", + "$pulseaudio_build_path/include", + "../../../../../interfaces/inner_api/native/audiocommon/include", + ] + + cflags = [ + "-Wall", + "-Werror", + "-DHAVE_CONFIG_H", + "-D_GNU_SOURCE", + "-D__INCLUDED_FROM_PULSE_AUDIO", + ] +} + +ohos_shared_library("module-inner-capturer-sink") { + sanitize = { + cfi = true + debug = false + blocklist = "../../../../../cfi_blocklist.txt" + } + sources = [ "module_inner_capturer_sink.c" ] + + configs = [ ":capturer_config" ] + + cflags = [ "-DPA_MODULE_NAME=libmodule_inner_capturer_sink_z_so" ] + + ldflags = [ + "-Wl", + "--no-undefined", + ] + + deps = [ + "$pulseaudio_build_path/src:pulsecommon", + "$pulseaudio_build_path/src/pulse:pulse", + "$pulseaudio_build_path/src/pulsecore:pulsecore", + ] + + external_deps = [ "hiviewdfx_hilog_native:libhilog" ] + + subsystem_name = "multimedia" + part_name = "audio_framework" +} + +ohos_shared_library("module-receiver-sink") { + sanitize = { + cfi = true + debug = false + blocklist = "../../../../../cfi_blocklist.txt" + } + sources = [ "module_receiver_sink.c" ] + + configs = [ ":capturer_config" ] + + cflags = [ "-DPA_MODULE_NAME=libmodule_receiver_sink_z_so" ] + + ldflags = [ + "-Wl", + "--no-undefined", + ] + + deps = [ + "$pulseaudio_build_path/src:pulsecommon", + "$pulseaudio_build_path/src/pulse:pulse", + "$pulseaudio_build_path/src/pulsecore:pulsecore", + ] + + external_deps = [ "hiviewdfx_hilog_native:libhilog" ] + + subsystem_name = "multimedia" + part_name = "audio_framework" +} diff --git a/frameworks/native/pulseaudio/modules/capturer/module_inner_capturer_sink.c b/frameworks/native/pulseaudio/modules/capturer/module_inner_capturer_sink.c new file mode 100644 index 0000000000000000000000000000000000000000..f592f11d1640115ec37ceecd41a88d476080df13 --- /dev/null +++ b/frameworks/native/pulseaudio/modules/capturer/module_inner_capturer_sink.c @@ -0,0 +1,452 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "audio_log.h" + +PA_MODULE_AUTHOR("OpenHarmony"); +PA_MODULE_DESCRIPTION(_("Inner Capturer Sink")); +PA_MODULE_VERSION(PACKAGE_VERSION); +PA_MODULE_LOAD_ONCE(false); +PA_MODULE_USAGE( + "sink_name= " + "sink_properties= " + "format= " + "rate= " + "channels= " + "channel_map=" + "buffer_size=" + "formats="); + +#define DEFAULT_SINK_NAME "InnerCapturer" +#define DEFAULT_BUFFER_SIZE 8192 // same as HDI Sink +#define PA_ERR (-1) + +struct userdata { + pa_core *core; + pa_module *module; + pa_sink *sink; + + pa_thread *thread; + pa_thread_mq thread_mq; + pa_rtpoll *rtpoll; + + uint32_t buffer_size; + pa_usec_t block_usec; + pa_usec_t timestamp; + + pa_idxset *formats; +}; + +static const char * const VALID_MODARGS[] = { + "sink_name", + "sink_properties", + "format", + "rate", + "channels", + "channel_map", + "buffer_size" + "formats", + NULL +}; + +static int SinkProcessMsg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) +{ + switch (code) { + case PA_SINK_MESSAGE_GET_LATENCY: + *((int64_t*) data) = 0; + return 0; + default: + break; + } + + return pa_sink_process_msg(o, code, data, offset, chunk); +} + +/* Called from the IO thread. */ +static int SinkSetStateInIoThreadCb(pa_sink *s, pa_sink_state_t new_state, pa_suspend_cause_t new_suspend_cause) +{ + struct userdata *u; + + pa_assert(s); + pa_assert_se(u = s->userdata); + + if (s->thread_info.state == PA_SINK_SUSPENDED || s->thread_info.state == PA_SINK_INIT) { + if (PA_SINK_IS_OPENED(new_state)) { + u->timestamp = pa_rtclock_now(); + } + } + + return 0; +} + +static void SinkUpdateRequestedLatencyCb(pa_sink *s) +{ + struct userdata *u; + size_t nbytes; + + pa_sink_assert_ref(s); + pa_assert_se(u = s->userdata); + + u->block_usec = pa_sink_get_requested_latency_within_thread(s); + + if (u->block_usec == (pa_usec_t) -1) { + u->block_usec = s->thread_info.max_latency; + } + + nbytes = pa_usec_to_bytes(u->block_usec, &s->sample_spec); + pa_sink_set_max_rewind_within_thread(s, nbytes); + pa_sink_set_max_request_within_thread(s, nbytes); +} + +static void SinkReconfigureCb(pa_sink *s, pa_sample_spec *spec, bool passthrough) +{ + s->sample_spec = *spec; +} + +static bool SinkSetFormatsCb(pa_sink *s, pa_idxset *formats) +{ + struct userdata *u = s->userdata; + + pa_assert(u); + + pa_idxset_free(u->formats, (pa_free_cb_t) pa_format_info_free); + u->formats = pa_idxset_copy(formats, (pa_copy_func_t) pa_format_info_copy); + + return true; +} + +static pa_idxset* SinkGetFormatsCb(pa_sink *s) +{ + struct userdata *u = s->userdata; + + pa_assert(u); + + return pa_idxset_copy(u->formats, (pa_copy_func_t) pa_format_info_copy); +} + +static void ProcessRewind(struct userdata *u, pa_usec_t now) +{ + size_t rewindNbytes, inBuffer; + pa_usec_t delay; + + pa_assert(u); + + rewindNbytes = u->sink->thread_info.rewind_nbytes; + if (!PA_SINK_IS_OPENED(u->sink->thread_info.state) || rewindNbytes <= 0) { + goto do_nothing; + } + AUDIO_DEBUG_LOG("Requested to rewind %lu bytes.", (unsigned long) rewindNbytes); + + if (u->timestamp <= now) { + goto do_nothing; + } + + delay = u->timestamp - now; + inBuffer = pa_usec_to_bytes(delay, &u->sink->sample_spec); + if (inBuffer <= 0) { + goto do_nothing; + } + + if (rewindNbytes > inBuffer) { + rewindNbytes = inBuffer; + } + + pa_sink_process_rewind(u->sink, rewindNbytes); + u->timestamp -= pa_bytes_to_usec(rewindNbytes, &u->sink->sample_spec); + + AUDIO_DEBUG_LOG("Rewound %lu bytes.", (unsigned long) rewindNbytes); + return; + +do_nothing: + pa_sink_process_rewind(u->sink, 0); +} + +static void ProcessRender(struct userdata *u, pa_usec_t now) +{ + size_t ate = 0; + + pa_assert(u); + + /* This is the configured latency. Sink inputs connected to us + might not have a single frame more than the maxrequest value + queued. Hence: at maximum read this many bytes from the sink + inputs. */ + + /* Fill the buffer up the latency size */ + while (u->timestamp < now + u->block_usec) { + pa_memchunk chunk; + + pa_sink_render(u->sink, u->sink->thread_info.max_request, &chunk); + pa_memblock_unref(chunk.memblock); + + u->timestamp += pa_bytes_to_usec(chunk.length, &u->sink->sample_spec); + + ate += chunk.length; + if (ate >= u->sink->thread_info.max_request) { + break; + } + } +} + +static void ThreadFunc(void *userdata) +{ + struct userdata *u = userdata; + + pa_assert(u); + + AUDIO_DEBUG_LOG("Thread starting up"); + if (u->core->realtime_scheduling) { + pa_thread_make_realtime(u->core->realtime_priority); + } + + pa_thread_mq_install(&u->thread_mq); + + u->timestamp = pa_rtclock_now(); + + for (;;) { + pa_usec_t now = 0; + int ret; + + if (PA_SINK_IS_OPENED(u->sink->thread_info.state)) { + now = pa_rtclock_now(); + } + + if (PA_UNLIKELY(u->sink->thread_info.rewind_requested)) { + ProcessRewind(u, now); + } + + /* Render some data and drop it immediately */ + if (PA_SINK_IS_OPENED(u->sink->thread_info.state)) { + if (u->timestamp <= now) { + ProcessRender(u, now); + } + + pa_rtpoll_set_timer_absolute(u->rtpoll, u->timestamp); + } else { + pa_rtpoll_set_timer_disabled(u->rtpoll); + } + + /* Hmm, nothing to do. Let's sleep */ + if ((ret = pa_rtpoll_run(u->rtpoll)) < 0) { + goto fail; + } + + if (ret == 0) { + goto finish; + } + } + +fail: + /* If this was no regular exit from the loop we have to continue + * processing messages until we received PA_MESSAGE_SHUTDOWN */ + pa_asyncmsgq_post(u->thread_mq.outq, PA_MSGOBJECT(u->core), + PA_CORE_MESSAGE_UNLOAD_MODULE, u->module, 0, NULL, NULL); + pa_asyncmsgq_wait_for(u->thread_mq.inq, PA_MESSAGE_SHUTDOWN); + +finish: + AUDIO_DEBUG_LOG("Thread shutting down"); +} + +int InitFailed(pa_module *m, pa_modargs *ma) +{ + AUDIO_ERR_LOG("Inner Capturer Sink Init Failed"); + if (ma) + pa_modargs_free(ma); + + pa__done(m); + + return PA_ERR; +} + +int CreateSink(pa_module *m, pa_modargs *ma, struct userdata *u) +{ + pa_sample_spec ss; + pa_channel_map map; + pa_sink_new_data data; + pa_format_info *format; + + pa_assert(m); + + ss = m->core->default_sample_spec; + map = m->core->default_channel_map; + if (pa_modargs_get_sample_spec_and_channel_map(ma, &ss, &map, PA_CHANNEL_MAP_DEFAULT) < 0) { + AUDIO_ERR_LOG("Invalid sample format specification or channel map"); + return PA_ERR; + } + + pa_sink_new_data_init(&data); + data.driver = __FILE__; + data.module = m; + pa_sink_new_data_set_name(&data, pa_modargs_get_value(ma, "sink_name", DEFAULT_SINK_NAME)); + pa_sink_new_data_set_sample_spec(&data, &ss); + pa_sink_new_data_set_channel_map(&data, &map); + pa_proplist_sets(data.proplist, PA_PROP_DEVICE_DESCRIPTION, _("Null Output")); + pa_proplist_sets(data.proplist, PA_PROP_DEVICE_CLASS, "capturer"); + + u->formats = pa_idxset_new(NULL, NULL); + format = pa_format_info_new(); + format->encoding = PA_ENCODING_PCM; + pa_idxset_put(u->formats, format, NULL); + + if (pa_modargs_get_proplist(ma, "sink_properties", data.proplist, PA_UPDATE_REPLACE) < 0) { + AUDIO_ERR_LOG("Invalid properties"); + pa_sink_new_data_done(&data); + return PA_ERR; + } + + u->sink = pa_sink_new(m->core, &data, PA_SINK_LATENCY | PA_SINK_DYNAMIC_LATENCY); + pa_sink_new_data_done(&data); + + if (!u->sink) { + AUDIO_ERR_LOG("Failed to create sink."); + return PA_ERR; + } + + u->sink->parent.process_msg = SinkProcessMsg; + u->sink->set_state_in_io_thread = SinkSetStateInIoThreadCb; + u->sink->update_requested_latency = SinkUpdateRequestedLatencyCb; + u->sink->reconfigure = SinkReconfigureCb; + u->sink->get_formats = SinkGetFormatsCb; + u->sink->set_formats = SinkSetFormatsCb; + u->sink->userdata = u; + + return 0; +} + +int pa__init(pa_module *m) +{ + struct userdata *u = NULL; + pa_modargs *ma = NULL; + size_t nbytes; + + pa_assert(m); + + if (!(ma = pa_modargs_new(m->argument, VALID_MODARGS))) { + AUDIO_ERR_LOG("Failed to parse module arguments."); + return InitFailed(m, ma); + } + + m->userdata = u = pa_xnew0(struct userdata, 1); + u->core = m->core; + u->module = m; + u->rtpoll = pa_rtpoll_new(); + + if (pa_thread_mq_init(&u->thread_mq, m->core->mainloop, u->rtpoll) < 0) { + AUDIO_ERR_LOG("pa_thread_mq_init() failed."); + return InitFailed(m, ma); + } + + if (CreateSink(m, ma, u) != 0) { + return InitFailed(m, ma); + } + + pa_sink_set_asyncmsgq(u->sink, u->thread_mq.inq); + pa_sink_set_rtpoll(u->sink, u->rtpoll); + + u->buffer_size = DEFAULT_BUFFER_SIZE; + if (pa_modargs_get_value_u32(ma, "buffer_size", &u->buffer_size) < 0) { + AUDIO_ERR_LOG("Failed to parse buffer_size arg in capturer sink"); + return InitFailed(m, ma); + } + u->block_usec = pa_bytes_to_usec(u->buffer_size, &u->sink->sample_spec); + nbytes = pa_usec_to_bytes(u->block_usec, &u->sink->sample_spec); + + pa_sink_set_max_rewind(u->sink, nbytes); + + pa_sink_set_max_request(u->sink, u->buffer_size); + + if (!(u->thread = pa_thread_new("inner-capturer-sink", ThreadFunc, u))) { + AUDIO_ERR_LOG("Failed to create thread."); + return InitFailed(m, ma); + } + pa_sink_set_latency_range(u->sink, 0, u->block_usec); + + pa_sink_put(u->sink); + + pa_modargs_free(ma); + + return 0; +} + +int pa__get_n_used(pa_module *m) +{ + struct userdata *u; + + pa_assert(m); + pa_assert_se(u = m->userdata); + + return pa_sink_linked_by(u->sink); +} + +void pa__done(pa_module*m) +{ + struct userdata *u; + + pa_assert(m); + + if (!(u = m->userdata)) { + return; + } + + if (u->sink) { + pa_sink_unlink(u->sink); + } + + if (u->thread) { + pa_asyncmsgq_send(u->thread_mq.inq, NULL, PA_MESSAGE_SHUTDOWN, NULL, 0, NULL); + pa_thread_free(u->thread); + } + + pa_thread_mq_done(&u->thread_mq); + + if (u->sink) { + pa_sink_unref(u->sink); + } + + if (u->rtpoll) { + pa_rtpoll_free(u->rtpoll); + } + + if (u->formats) { + pa_idxset_free(u->formats, (pa_free_cb_t) pa_format_info_free); + } + + pa_xfree(u); +} diff --git a/frameworks/native/pulseaudio/modules/capturer/module_receiver_sink.c b/frameworks/native/pulseaudio/modules/capturer/module_receiver_sink.c new file mode 100644 index 0000000000000000000000000000000000000000..f4b3c2655f40d606128d5c0efb9ec15a50dea74a --- /dev/null +++ b/frameworks/native/pulseaudio/modules/capturer/module_receiver_sink.c @@ -0,0 +1,664 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "securec.h" +#include "audio_log.h" + +PA_MODULE_AUTHOR("OpenHarmony"); +PA_MODULE_DESCRIPTION("Virtual channel sink"); +PA_MODULE_VERSION(PACKAGE_VERSION); +PA_MODULE_LOAD_ONCE(false); +PA_MODULE_USAGE( + "sink_name= " + "master= " +); + +#define DEFAULT_BUFFER_SIZE 8192 + +struct userdata { + pa_module *module; + + pa_sink *sink; + pa_sink_input *sink_input; + + uint32_t buffer_size; + pa_core *core; + + pa_usec_t block_usec; + pa_usec_t timestamp; + + pa_rtpoll *rtpoll; + pa_thread *thread; + pa_thread_mq thread_mq; + + pa_sample_spec sampleSpec; + pa_channel_map sinkMap; + + bool auto_desc; +}; + +static const char * const VALID_MODARGS[] = { + "sink_name", + "master", + "buffer_size", + NULL +}; + +static void ProcessRender(struct userdata *u, pa_usec_t now) +{ + size_t ate = 0; + pa_assert(u); + + /* This is the configured latency. Sink inputs connected to us + might not have a single frame more than the maxrequest value + queued. Hence: at maximum read this many bytes from the sink + inputs. */ + + /* Fill the buffer up the latency size */ + while (u->timestamp < now + u->block_usec) { + pa_memchunk chunk; + + pa_sink_render(u->sink, u->sink->thread_info.max_request, &chunk); + pa_memblock_unref(chunk.memblock); + + u->timestamp += pa_bytes_to_usec(chunk.length, &u->sink->sample_spec); + + ate += chunk.length; + + if (ate >= u->sink->thread_info.max_request) { + break; + } + } +} + +/* Called from I/O thread context */ +static int SinkPorcessMsg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) +{ + struct userdata *u = PA_SINK(o)->userdata; + + switch (code) { + case PA_SINK_MESSAGE_GET_LATENCY: + /* The sink is _put() before the sink input is, so let's + * make sure we don't access it yet */ + if (!PA_SINK_IS_LINKED(u->sink->thread_info.state) || + !PA_SINK_INPUT_IS_LINKED(u->sink_input->thread_info.state)) { + *((int64_t*) data) = 0; + return 0; + } + + *((int64_t*) data) = + /* Get the latency of the master sink */ + pa_sink_get_latency_within_thread(u->sink_input->sink, true) + + + /* Add the latency internal to our sink input on top */ + pa_bytes_to_usec(pa_memblockq_get_length(u->sink_input->thread_info.render_memblockq), + &u->sink_input->sink->sample_spec); + return 0; + default: + break; + } + return pa_sink_process_msg(o, code, data, offset, chunk); +} + +/* Called from main context */ +static int SinkSetStateInMainThread(pa_sink *s, pa_sink_state_t state, pa_suspend_cause_t suspend_cause) +{ + struct userdata *u; + + pa_sink_assert_ref(s); + pa_assert_se(u = s->userdata); + + if (!PA_SINK_IS_LINKED(state) || + !PA_SINK_INPUT_IS_LINKED(u->sink_input->state)) { + return 0; + } + + pa_sink_input_cork(u->sink_input, state == PA_SINK_SUSPENDED); + return 0; +} + +/* Called from the IO thread. */ +static int SinkSetStateInIoThreadCb(pa_sink *s, pa_sink_state_t new_state, pa_suspend_cause_t new_suspend_cause) +{ + struct userdata *u; + + pa_assert(s); + pa_assert_se(u = s->userdata); + + /* When set to running or idle for the first time, request a rewind + * of the master sink to make sure we are heard immediately */ + if (PA_SINK_IS_OPENED(new_state) && s->thread_info.state == PA_SINK_INIT) { + AUDIO_DEBUG_LOG("Requesting rewind due to state change."); + pa_sink_input_request_rewind(u->sink_input, 0, false, true, true); + } + + return 0; +} + +/* Called from I/O thread context */ +static void SinkRequestRewind(pa_sink *s) +{ + struct userdata *u; + + pa_sink_assert_ref(s); + pa_assert_se(u = s->userdata); + + if (!PA_SINK_IS_LINKED(u->sink->thread_info.state) || + !PA_SINK_INPUT_IS_LINKED(u->sink_input->thread_info.state)) { + return; + } + + pa_sink_input_request_rewind(u->sink_input, s->thread_info.rewind_nbytes, true, false, false); +} + +/* Called from I/O thread context */ +static void SinkUpdateRequestedLatency(pa_sink *s) +{ + struct userdata *u; + + pa_sink_assert_ref(s); + pa_assert_se(u = s->userdata); + + if (!PA_SINK_IS_LINKED(u->sink->thread_info.state) || + !PA_SINK_INPUT_IS_LINKED(u->sink_input->thread_info.state)) { + return; + } + + /* Just hand this one over to the master sink */ + pa_sink_input_set_requested_latency_within_thread( + u->sink_input, pa_sink_get_requested_latency_within_thread(s)); +} + +/* Called from I/O thread context */ +static int SinkInputPopCb(pa_sink_input *i, size_t nbytes, pa_memchunk *chunk) +{ + struct userdata *u; + + pa_sink_input_assert_ref(i); + pa_assert(chunk); + pa_assert_se(u = i->userdata); + + if (!PA_SINK_IS_LINKED(u->sink->thread_info.state)) { + return -1; + } + + /* Hmm, process any rewind request that might be queued up */ + pa_sink_process_rewind(u->sink, 0); + + pa_sink_render(u->sink, nbytes, chunk); + return 0; +} + +/* Called from I/O thread context */ +static void SinkInputProcessRewindCb(pa_sink_input *i, size_t nbytes) +{ + size_t amount = 0; + struct userdata *u; + + pa_sink_input_assert_ref(i); + pa_assert_se(u = i->userdata); + + /* If the sink is not yet linked, there is nothing to rewind */ + if (!PA_SINK_IS_LINKED(u->sink->thread_info.state)) { + return; + } + + if (u->sink->thread_info.rewind_nbytes > 0) { + amount = PA_MIN(u->sink->thread_info.rewind_nbytes, nbytes); + u->sink->thread_info.rewind_nbytes = 0; + } + + pa_sink_process_rewind(u->sink, amount); +} + +/* Called from I/O thread context */ +static void SinkInputUpdateMaxRewindCb(pa_sink_input *i, size_t nbytes) +{ + struct userdata *u; + + pa_sink_input_assert_ref(i); + pa_assert_se(u = i->userdata); + + pa_sink_set_max_rewind_within_thread(u->sink, nbytes); +} + +/* Called from I/O thread context */ +static void SinkInputUpdateMaxRequestCb(pa_sink_input *i, size_t nbytes) +{ + struct userdata *u; + + pa_sink_input_assert_ref(i); + pa_assert_se(u = i->userdata); + + pa_sink_set_max_request_within_thread(u->sink, nbytes); +} + +/* Called from I/O thread context */ +static void SinkInputUpdateSinkLatencyRangeCb(pa_sink_input *i) +{ + struct userdata *u; + + pa_sink_input_assert_ref(i); + pa_assert_se(u = i->userdata); + + pa_sink_set_latency_range_within_thread(u->sink, i->sink->thread_info.min_latency, + i->sink->thread_info.max_latency); +} + +/* Called from I/O thread context */ +static void SinkInputUpdateSinkFixedLatencyCb(pa_sink_input *i) +{ + struct userdata *u; + + pa_sink_input_assert_ref(i); + pa_assert_se(u = i->userdata); + + pa_sink_set_fixed_latency_within_thread(u->sink, i->sink->thread_info.fixed_latency); +} + +/* Called from I/O thread context */ +static void SinkInputDetachCb(pa_sink_input *i) +{ + struct userdata *u; + + pa_sink_input_assert_ref(i); + pa_assert_se(u = i->userdata); + + if (PA_SINK_IS_LINKED(u->sink->thread_info.state)) { + pa_sink_detach_within_thread(u->sink); + } + + pa_sink_set_rtpoll(u->sink, NULL); +} + +/* Called from I/O thread context */ +static void SinkInputAttachCb(pa_sink_input *i) +{ + struct userdata *u; + + pa_sink_input_assert_ref(i); + pa_assert_se(u = i->userdata); + + pa_sink_set_rtpoll(u->sink, i->sink->thread_info.rtpoll); + pa_sink_set_latency_range_within_thread(u->sink, i->sink->thread_info.min_latency, + i->sink->thread_info.max_latency); + pa_sink_set_fixed_latency_within_thread(u->sink, i->sink->thread_info.fixed_latency); + pa_sink_set_max_request_within_thread(u->sink, pa_sink_input_get_max_request(i)); + pa_sink_set_max_rewind_within_thread(u->sink, pa_sink_input_get_max_rewind(i)); + + if (PA_SINK_IS_LINKED(u->sink->thread_info.state)) { + pa_sink_attach_within_thread(u->sink); + } +} + +/* Called from main context */ +static void SinkInputKillCb(pa_sink_input *i) +{ + struct userdata *u; + + pa_sink_input_assert_ref(i); + pa_assert_se(u = i->userdata); + + /* The order here matters! We first kill the sink so that streams + * can properly be moved away while the sink input is still connected + * to the master. */ + pa_sink_input_cork(u->sink_input, true); + pa_sink_unlink(u->sink); + pa_sink_input_unlink(u->sink_input); + + pa_sink_input_unref(u->sink_input); + u->sink_input = NULL; + + pa_sink_unref(u->sink); + u->sink = NULL; + + pa_module_unload_request(u->module, true); +} + +/* Called from main context */ +static void SinkInputMovingCb(pa_sink_input *i, pa_sink *dest) +{ + struct userdata *u; + + pa_sink_input_assert_ref(i); + pa_assert_se(u = i->userdata); + + if (dest) { + pa_sink_set_asyncmsgq(u->sink, dest->asyncmsgq); + pa_sink_update_flags(u->sink, PA_SINK_LATENCY|PA_SINK_DYNAMIC_LATENCY, dest->flags); + } else { + pa_sink_set_asyncmsgq(u->sink, NULL); + } + + if (u->auto_desc && dest) { + const char *k; + pa_proplist *pl; + + pl = pa_proplist_new(); + k = pa_proplist_gets(dest->proplist, PA_PROP_DEVICE_DESCRIPTION); + pa_proplist_setf(pl, PA_PROP_DEVICE_DESCRIPTION, "Remapped %s", k ? k : dest->name); + + pa_sink_update_proplist(u->sink, PA_UPDATE_REPLACE, pl); + pa_proplist_free(pl); + } +} + +static void ThreadFunc(void *userdata) +{ + struct userdata *u = userdata; + + pa_assert(u); + + AUDIO_DEBUG_LOG("Receiver sink thread starting up"); + + pa_thread_mq_install(&u->thread_mq); + + u->timestamp = pa_rtclock_now(); + + for (;;) { + pa_usec_t now = 0; + int ret; + + if (PA_SINK_IS_OPENED(u->sink->thread_info.state)) { + now = pa_rtclock_now(); + } + + /* Render some data and drop it immediately */ + if (PA_SINK_IS_OPENED(u->sink->thread_info.state)) { + if (u->timestamp <= now) { + ProcessRender(u, now); + } + + pa_rtpoll_set_timer_absolute(u->rtpoll, u->timestamp); + } else { + pa_rtpoll_set_timer_disabled(u->rtpoll); + } + + /* Hmm, nothing to do. Let's sleep */ + if ((ret = pa_rtpoll_run(u->rtpoll)) < 0) { + goto fail; + } + + if (ret == 0) { + goto finish; + } + } + +fail: + /* If this was no regular exit from the loop we have to continue + * processing messages until we received PA_MESSAGE_SHUTDOWN */ + pa_asyncmsgq_post(u->thread_mq.outq, PA_MSGOBJECT(u->core), + PA_CORE_MESSAGE_UNLOAD_MODULE, u->module, 0, NULL, NULL); + pa_asyncmsgq_wait_for(u->thread_mq.inq, PA_MESSAGE_SHUTDOWN); + +finish: + AUDIO_DEBUG_LOG("Thread shutting down"); +} + +int InitFail(pa_module *m, pa_modargs *ma) +{ + AUDIO_ERR_LOG("Receiver sink init failed."); + if (ma) + pa_modargs_free(ma); + pa__done(m); + return -1; +} + +int CreateSink(pa_module *m, pa_modargs *ma, pa_sink *master, struct userdata *u) +{ + pa_sample_spec ss; + pa_channel_map sinkMap; + pa_sink_new_data sinkData; + + ss = master->sample_spec; + sinkMap = master->channel_map; + if (pa_modargs_get_sample_spec_and_channel_map(ma, &ss, &sinkMap, PA_CHANNEL_MAP_DEFAULT) < 0) { + AUDIO_ERR_LOG("Invalid sample format specification or channel map"); + return -1; + } + + pa_sink_new_data_init(&sinkData); + sinkData.driver = __FILE__; + sinkData.module = m; + if (!(sinkData.name = pa_xstrdup(pa_modargs_get_value(ma, "sink_name", NULL)))) + sinkData.name = pa_sprintf_malloc("%s.remapped", master->name); + pa_sink_new_data_set_sample_spec(&sinkData, &ss); + pa_sink_new_data_set_channel_map(&sinkData, &sinkMap); + pa_proplist_sets(sinkData.proplist, PA_PROP_DEVICE_MASTER_DEVICE, master->name); + pa_proplist_sets(sinkData.proplist, PA_PROP_DEVICE_CLASS, "filter"); + + if (pa_modargs_get_proplist(ma, "sink_properties", sinkData.proplist, PA_UPDATE_REPLACE) < 0) { + AUDIO_ERR_LOG("Invalid properties"); + pa_sink_new_data_done(&sinkData); + return -1; + } + + if ((u->auto_desc = !pa_proplist_contains(sinkData.proplist, PA_PROP_DEVICE_DESCRIPTION))) { + const char *k; + + k = pa_proplist_gets(master->proplist, PA_PROP_DEVICE_DESCRIPTION); + pa_proplist_setf(sinkData.proplist, PA_PROP_DEVICE_DESCRIPTION, "Remapped %s", k ? k : master->name); + } + + u->sink = pa_sink_new(m->core, &sinkData, master->flags & (PA_SINK_LATENCY|PA_SINK_DYNAMIC_LATENCY)); + pa_sink_new_data_done(&sinkData); + + if (!u->sink) { + return -1; + } + + u->sink->parent.process_msg = SinkPorcessMsg; + u->sink->set_state_in_main_thread = SinkSetStateInMainThread; + u->sink->set_state_in_io_thread = SinkSetStateInIoThreadCb; + u->sink->update_requested_latency = SinkUpdateRequestedLatency; + u->sink->request_rewind = SinkRequestRewind; + u->sink->userdata = u; + u->sampleSpec = ss; + u->sinkMap = sinkMap; + + pa_sink_set_asyncmsgq(u->sink, master->asyncmsgq); + return 0; +} + +int CreateSinkInput(pa_module *m, pa_modargs *ma, pa_sink *master, struct userdata *u) +{ + pa_sink_input_new_data sinkInputData; + pa_resample_method_t resampleMethod = PA_RESAMPLER_SRC_SINC_FASTEST; + + if (pa_modargs_get_resample_method(ma, &resampleMethod) < 0) { + AUDIO_ERR_LOG("Invalid resampling method"); + return -1; + } + + if (u->sinkMap.channels != u->sampleSpec.channels) { + AUDIO_ERR_LOG("Number of channels doesn't match"); + return -1; + } + + if (pa_channel_map_equal(&u->sinkMap, &master->channel_map)) { + AUDIO_WARNING_LOG("Number of channels doesn't match of [%{public}s] and [%{public}s]", + u->sink->name, master->name); + } + + pa_sink_input_new_data_init(&sinkInputData); + sinkInputData.driver = __FILE__; + sinkInputData.module = m; + pa_sink_input_new_data_set_sink(&sinkInputData, master, false, true); + sinkInputData.origin_sink = u->sink; + pa_proplist_sets(sinkInputData.proplist, PA_PROP_MEDIA_NAME, "Remapped Stream"); + pa_proplist_sets(sinkInputData.proplist, PA_PROP_MEDIA_ROLE, "filter"); + pa_sink_input_new_data_set_sample_spec(&sinkInputData, &u->sampleSpec); + pa_sink_input_new_data_set_channel_map(&sinkInputData, &u->sinkMap); + sinkInputData.flags = PA_SINK_INPUT_START_CORKED; + sinkInputData.resample_method = PA_RESAMPLER_SRC_SINC_FASTEST; + + pa_sink_input_new(&u->sink_input, m->core, &sinkInputData); + pa_sink_input_new_data_done(&sinkInputData); + + if (!u->sink_input) { + return -1; + } + + u->sink_input->pop = SinkInputPopCb; + u->sink_input->process_rewind = SinkInputProcessRewindCb; + u->sink_input->update_max_rewind = SinkInputUpdateMaxRewindCb; + u->sink_input->update_max_request = SinkInputUpdateMaxRequestCb; + u->sink_input->update_sink_latency_range = SinkInputUpdateSinkLatencyRangeCb; + u->sink_input->update_sink_fixed_latency = SinkInputUpdateSinkFixedLatencyCb; + u->sink_input->attach = SinkInputAttachCb; + u->sink_input->detach = SinkInputDetachCb; + u->sink_input->kill = SinkInputKillCb; + u->sink_input->moving = SinkInputMovingCb; + u->sink_input->userdata = u; + + u->sink->input_to_master = u->sink_input; + return 0; +} + +int pa__init(pa_module *m) +{ + struct userdata *u; + pa_modargs *ma; + pa_sink *master; + + pa_assert(m); + + if (!(ma = pa_modargs_new(m->argument, VALID_MODARGS))) { + AUDIO_ERR_LOG("Failed to parse module arguments."); + return InitFail(m, ma); + } + + if (!(master = pa_namereg_get(m->core, pa_modargs_get_value(ma, "master", NULL), PA_NAMEREG_SINK))) { + pa_log("Master sink not found"); + return InitFail(m, ma); + } + + u = pa_xnew0(struct userdata, 1); + u->core = m->core; + u->module = m; + m->userdata = u; + u->rtpoll = pa_rtpoll_new(); + + if (pa_thread_mq_init(&u->thread_mq, m->core->mainloop, u->rtpoll) < 0) { + AUDIO_ERR_LOG("pa_thread_mq_init failed."); + return InitFail(m, ma); + } + + /* Create sink */ + if (CreateSink(m, ma, master, u) < 0) { + AUDIO_ERR_LOG("CreateSink failed."); + return InitFail(m, ma); + } + pa_sink_set_rtpoll(u->sink, u->rtpoll); + u->buffer_size = DEFAULT_BUFFER_SIZE; + if (pa_modargs_get_value_u32(ma, "buffer_size", &u->buffer_size) < 0) { + AUDIO_ERR_LOG("Failed to get buffer_size argument in effect sink"); + return InitFail(m, ma); + } + u->block_usec = pa_bytes_to_usec(u->buffer_size, &u->sink->sample_spec); + pa_sink_set_latency_range(u->sink, 0, u->block_usec); + pa_sink_set_max_request(u->sink, u->buffer_size); + if (!(u->thread = pa_thread_new("receiver-sink", ThreadFunc, u))) { + return InitFail(m, ma); + } + + /* Create sink input */ + if (CreateSinkInput(m, ma, master, u) < 0) { + AUDIO_ERR_LOG("CreateSinkInput failed."); + return InitFail(m, ma); + } + + /* The order here is important. The input must be put first, + * otherwise streams might attach to the sink before the sink + * input is attached to the master. */ + pa_sink_input_put(u->sink_input); + pa_sink_put(u->sink); + pa_sink_input_cork(u->sink_input, false); + + pa_modargs_free(ma); + + return 0; +} + +int pa__get_n_used(pa_module *m) +{ + struct userdata *u; + + pa_assert(m); + pa_assert_se(u = m->userdata); + + return pa_sink_linked_by(u->sink); +} + +void pa__done(pa_module *m) +{ + struct userdata *u; + + pa_assert(m); + + if (!(u = m->userdata)) { + return; + } + + /* See comments in SinkInputKillCb() above regarding + * destruction order! */ + + if (u->sink_input) { + pa_sink_input_cork(u->sink_input, true); + } + + if (u->sink) { + pa_sink_unlink(u->sink); + } + + if (u->thread) { + pa_asyncmsgq_send(u->thread_mq.inq, NULL, PA_MESSAGE_SHUTDOWN, NULL, 0, NULL); + pa_thread_free(u->thread); + } + pa_thread_mq_done(&u->thread_mq); + + if (u->sink_input) { + pa_sink_input_unlink(u->sink_input); + pa_sink_input_unref(u->sink_input); + } + + if (u->sink) { + pa_sink_unref(u->sink); + } + + if (u->rtpoll) { + pa_rtpoll_free(u->rtpoll); + } + + pa_xfree(u); +} diff --git a/frameworks/native/pulseaudio/modules/cluster/BUILD.gn b/frameworks/native/pulseaudio/modules/cluster/BUILD.gn index f540c692fbffcdd778c9c81fd6a44740919c43f3..029b2cac017d3a76a693a5b8ee99c9be847c083a 100644 --- a/frameworks/native/pulseaudio/modules/cluster/BUILD.gn +++ b/frameworks/native/pulseaudio/modules/cluster/BUILD.gn @@ -27,6 +27,7 @@ config("cluster_config") { "$pulseaudio_build_path/src", "$pulseaudio_build_path/include", "../../../audioeffect/include", + "../../../playbackcapturer/include", "../../../../../interfaces/inner_api/native/audiocommon/include", ] @@ -57,6 +58,7 @@ ohos_shared_library("module-cluster-sink") { "$pulseaudio_build_path/src/pulsecore:pulsecore", "${third_party_path}/bounds_checking_function:libsec_shared", "../../../audioeffect:audio_effect", + "../../../playbackcapturer:playback_capturer", ] external_deps = [ "hiviewdfx_hilog_native:libhilog" ] diff --git a/frameworks/native/pulseaudio/modules/cluster/module_cluster_sink.c b/frameworks/native/pulseaudio/modules/cluster/module_cluster_sink.c index bf414d71ce4470925cc05d13ccde0deb0ba5b5f8..0d3ed15ef51d957fd3858c0eb87d7aa1b745dfe7 100644 --- a/frameworks/native/pulseaudio/modules/cluster/module_cluster_sink.c +++ b/frameworks/native/pulseaudio/modules/cluster/module_cluster_sink.c @@ -26,6 +26,7 @@ #include "audio_effect_chain_adapter.h" #include "audio_log.h" +#include "playback_capturer_adapter.h" PA_MODULE_AUTHOR("OpenHarmony"); PA_MODULE_DESCRIPTION(_("Cluster module")); @@ -38,6 +39,8 @@ PA_MODULE_USAGE( struct userdata { pa_core *core; pa_module *module; + + bool isInnerCapturer; }; static const char * const VALID_MODARGS[] = { @@ -53,6 +56,37 @@ static pa_hook_result_t MoveSinkInputIntoSink(pa_sink_input *si, pa_sink *sink) return PA_HOOK_OK; } +static bool IsSinkInputSupportInnerCapturer(pa_sink_input *si, struct userdata *u) +{ + pa_assert(si); + pa_assert(u); + + // check if at an inner capturer scene. + if (!u->isInnerCapturer) { + return false; + } + + const char *usageStr = pa_proplist_gets(si->proplist, "stream.usage"); + const char *privacyTypeStr = pa_proplist_gets(si->proplist, "stream.privacyType"); + int32_t usage = -1; + int32_t privacyType = -1; + bool usageSupport = false; + bool privacySupport = true; + + if (privacyTypeStr != NULL) { + pa_atoi(privacyTypeStr, &privacyType); + privacySupport = IsPrivacySupportInnerCapturer(privacyType); + } + + if (usageStr != NULL) { + pa_atoi(usageStr, &usage); + usageSupport = IsStreamSupportInnerCapturer(usage); + } + + AUDIO_DEBUG_LOG("get privacyType:%{public}d, usage:%{public}d of sink input:%{public}d", privacyType, usage, si->index); + return privacySupport && usageSupport; +} + static pa_hook_result_t SinkInputProplistChangedCb(pa_core *c, pa_sink_input *si, struct userdata *u) { pa_sink *effectSink; @@ -60,6 +94,9 @@ static pa_hook_result_t SinkInputProplistChangedCb(pa_core *c, pa_sink_input *si pa_assert(u); const char *sceneMode = pa_proplist_gets(si->proplist, "scene.mode"); const char *sceneType = pa_proplist_gets(si->proplist, "scene.type"); + if (pa_safe_streq(si->sink->name, "InnerCapturer")) { + return PA_HOOK_OK; + } const char *deviceString = pa_proplist_gets(si->sink->proplist, PA_PROP_DEVICE_STRING); if (pa_safe_streq(deviceString, "remote")) { @@ -71,23 +108,71 @@ static pa_hook_result_t SinkInputProplistChangedCb(pa_core *c, pa_sink_input *si return PA_HOOK_OK; } + const char *receiverSinkName = "Receiver"; + pa_sink *receiverSink = pa_namereg_get(c, receiverSinkName, PA_NAMEREG_SINK); + bool isSupportInnerCapturer = IsSinkInputSupportInnerCapturer(si, u); + bool innerCapturerFlag = u->isInnerCapturer && receiverSink != NULL && isSupportInnerCapturer && sceneType != NULL; + bool existFlag = EffectChainManagerExist(sceneType, sceneMode); const char *clientUid = pa_proplist_gets(si->proplist, "stream.client.uid"); // if EFFECT_NONE mode or effect chain does not exist if (pa_safe_streq(clientUid, "1003") || pa_safe_streq(sceneMode, "EFFECT_NONE") || !existFlag) { - return MoveSinkInputIntoSink(si, c->default_sink); //if bypass move to hdi sink + if (innerCapturerFlag) { + return MoveSinkInputIntoSink(si, receiverSink); // playback capturer + } else { + return MoveSinkInputIntoSink(si, c->default_sink); //if bypass move to hdi sink + } } - effectSink = pa_namereg_get(c, sceneType, PA_NAMEREG_SINK); + const char *sinkName = innerCapturerFlag ? pa_sprintf_malloc("%s_CAP", sceneType) : sceneType; + effectSink = pa_namereg_get(c, sinkName, PA_NAMEREG_SINK); if (!effectSink) { // if sink does not exist AUDIO_ERR_LOG("Effect sink [%{public}s] sink not found.", sceneType); - // classify sinkinput to default sink - MoveSinkInputIntoSink(si, c->default_sink); + if (innerCapturerFlag) { + MoveSinkInputIntoSink(si, receiverSink); + } else { + MoveSinkInputIntoSink(si, c->default_sink); // classify sinkinput to default sink + } + } else { + MoveSinkInputIntoSink(si, effectSink); // classify sinkinput to effect sink + } + + return PA_HOOK_OK; +} + +static pa_hook_result_t SourceOutputStateChangedCb(pa_core *c, pa_source_output *so, struct userdata *u) +{ + uint32_t idx; + pa_sink_input *si; + int innerCapturerFlag = 0; + + pa_assert(c); + pa_assert(u); + pa_assert(so); + + const char *flag = pa_proplist_gets(so->proplist, "stream.isInnerCapturer"); + if (flag != NULL) { + pa_atoi(flag, &innerCapturerFlag); + } + + if (innerCapturerFlag == 0) { + u->isInnerCapturer = false; + return PA_HOOK_OK; } else { - // classify sinkinput to effect sink - MoveSinkInputIntoSink(si, effectSink); + u->isInnerCapturer = true; } + if (so->state != PA_SOURCE_OUTPUT_RUNNING) { + return PA_HOOK_OK; + } + + PA_IDXSET_FOREACH(si, c->sink_inputs, idx) { + const char *moduleName = si->module->name; + if (pa_safe_streq(moduleName, "libmodule-effect-sink.z.so")) { + continue; + } + SinkInputProplistChangedCb(c, si, u); + } return PA_HOOK_OK; } @@ -135,6 +220,8 @@ int pa__init(pa_module *m) (pa_hook_cb_t)SinkInputProplistChangedCb, u); pa_module_hook_connect(m, &m->core->hooks[PA_CORE_HOOK_CLIENT_PROPLIST_CHANGED], PA_HOOK_LATE, (pa_hook_cb_t)ClientProplistChangedCb, u); + pa_module_hook_connect(m, &m->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_STATE_CHANGED], PA_HOOK_LATE, + (pa_hook_cb_t)SourceOutputStateChangedCb, u); pa_modargs_free(ma); diff --git a/frameworks/native/pulseaudio/modules/effect/module_effect_sink.c b/frameworks/native/pulseaudio/modules/effect/module_effect_sink.c index 3dde350765ad3068e371438523ca8cc46136d77a..7264e6f201fadb97ebc6fb8c9a778a0986be4bb2 100644 --- a/frameworks/native/pulseaudio/modules/effect/module_effect_sink.c +++ b/frameworks/native/pulseaudio/modules/effect/module_effect_sink.c @@ -45,6 +45,8 @@ struct userdata { pa_module *module; pa_sink *sink; pa_sink_input *sinkInput; + char *sceneName; + pa_sample_spec sampleSpec; pa_channel_map sinkMap; BufferAttr *bufferAttr; @@ -57,6 +59,7 @@ struct userdata { static const char * const VALID_MODARGS[] = { "sink_name", "rate", + "scene_name", NULL }; @@ -310,6 +313,18 @@ static size_t MemblockqMissing(pa_memblockq *bq) } // END Utility functions +static void EffectProcess(pa_sink_input *si, struct userdata *u) +{ + const char *sn; + sn = pa_proplist_gets(si->proplist, "scene.name"); + if (sn != NULL) { + char *sceneName = pa_sprintf_malloc("%s", sn); + EffectChainManagerProcess(sceneName, u->bufferAttr); + } else { + EffectChainManagerProcess(si->origin_sink->name, u->bufferAttr); + } +} + /* Called from I/O thread context */ static int SinkInputPopCb(pa_sink_input *si, size_t nbytes, pa_memchunk *chunk) { @@ -358,8 +373,8 @@ static int SinkInputPopCb(pa_sink_input *si, size_t nbytes, pa_memchunk *chunk) pa_memblock_release(tchunk.memblock); pa_memblock_unref(tchunk.memblock); - EffectChainManagerProcess(si->origin_sink->name, u->bufferAttr); - + EffectProcess(si, u); + ConvertFromFloat(u->format, u->processLen, bufOut, dst); dst += u->processSize; @@ -514,7 +529,7 @@ static void SinkInputMovingCb(pa_sink_input *i, pa_sink *dest) pa_sink_set_asyncmsgq(u->sink, NULL); return; } - + pa_sink_set_asyncmsgq(u->sink, dest->asyncmsgq); pa_sink_update_flags(u->sink, PA_SINK_LATENCY | PA_SINK_DYNAMIC_LATENCY, dest->flags); @@ -543,21 +558,27 @@ int CreateSink(pa_module *m, pa_modargs *ma, pa_sink *masterSink, struct userdat pa_sample_spec ss; pa_channel_map sinkMap; pa_sink_new_data sinkData; + const char *scene; /* Create sink */ ss = m->core->default_sample_spec; sinkMap = m->core->default_channel_map; if (pa_modargs_get_sample_spec_and_channel_map(ma, &ss, &sinkMap, PA_CHANNEL_MAP_DEFAULT) < 0) { AUDIO_ERR_LOG("Invalid sample format specification or channel map"); - return InitFail(m, ma); + return -1; } - + pa_sink_new_data_init(&sinkData); sinkData.driver = __FILE__; sinkData.module = m; if (!(sinkData.name = pa_xstrdup(pa_modargs_get_value(ma, "sink_name", NULL)))) { sinkData.name = pa_sprintf_malloc("%s.effected", masterSink->name); } + + scene = pa_modargs_get_value(ma, "scene_name", NULL); + if (scene != NULL) { + u->sceneName = pa_sprintf_malloc("%s", scene); + } pa_sink_new_data_set_sample_spec(&sinkData, &ss); pa_sink_new_data_set_channel_map(&sinkData, &sinkMap); pa_proplist_sets(sinkData.proplist, PA_PROP_DEVICE_MASTER_DEVICE, masterSink->name); @@ -566,12 +587,12 @@ int CreateSink(pa_module *m, pa_modargs *ma, pa_sink *masterSink, struct userdat const char *k; k = pa_proplist_gets(masterSink->proplist, PA_PROP_DEVICE_DESCRIPTION); pa_proplist_setf(sinkData.proplist, PA_PROP_DEVICE_DESCRIPTION, "Remapped %s", k ? k : masterSink->name); - + u->sink = pa_sink_new(m->core, &sinkData, masterSink->flags & (PA_SINK_LATENCY | PA_SINK_DYNAMIC_LATENCY)); pa_sink_new_data_done(&sinkData); if (!u->sink) { - return InitFail(m, ma); + return -1; } u->sink->parent.process_msg = SinkProcessMsg; @@ -582,7 +603,7 @@ int CreateSink(pa_module *m, pa_modargs *ma, pa_sink *masterSink, struct userdat u->sink->userdata = u; u->sampleSpec = ss; u->sinkMap = sinkMap; - + pa_sink_set_asyncmsgq(u->sink, masterSink->asyncmsgq); return 0; } @@ -603,6 +624,9 @@ int CreateSinkInput(pa_module *m, pa_modargs *ma, pa_sink *masterSink, struct us pa_proplist_sets(sinkInputData.proplist, PA_PROP_MEDIA_ROLE, "filter"); pa_proplist_sets(sinkInputData.proplist, "scene.type", "N/A"); pa_proplist_sets(sinkInputData.proplist, "scene.mode", "N/A"); + if (u->sceneName != NULL) { + pa_proplist_sets(sinkInputData.proplist, "scene.name", u->sceneName); + } pa_sink_input_new_data_set_sample_spec(&sinkInputData, &u->sampleSpec); pa_sink_input_new_data_set_channel_map(&sinkInputData, &u->sinkMap); sinkInputData.flags = (remix ? 0 : PA_SINK_INPUT_NO_REMIX) | PA_SINK_INPUT_START_CORKED; @@ -612,7 +636,7 @@ int CreateSinkInput(pa_module *m, pa_modargs *ma, pa_sink *masterSink, struct us pa_sink_input_new_data_done(&sinkInputData); if (!u->sinkInput) { - return InitFail(m, ma); + return -1; } u->sinkInput->pop = SinkInputPopCb; @@ -631,9 +655,40 @@ int CreateSinkInput(pa_module *m, pa_modargs *ma, pa_sink *masterSink, struct us return 0; } -int pa__init(pa_module *m) +int ConfigSinkInput(struct userdata *u) { + // Set buffer attributes + int32_t frameLen = EffectChainManagerGetFrameLen(); + u->format = u->sampleSpec.format; + u->processLen = u->sampleSpec.channels * frameLen; + u->processSize = u->processLen * sizeof(float); int ret; + + u->bufferAttr = pa_xnew0(BufferAttr, 1); + pa_assert_se(u->bufferAttr->bufIn = (float *)malloc(u->processSize)); + pa_assert_se(u->bufferAttr->bufOut = (float *)malloc(u->processSize)); + u->bufferAttr->samplingRate = u->sampleSpec.rate; + u->bufferAttr->frameLen = frameLen; + u->bufferAttr->numChanIn = u->sampleSpec.channels; + u->bufferAttr->numChanOut = u->sampleSpec.channels; + if (u->sceneName != NULL) { + ret = EffectChainManagerCreate(u->sceneName, u->bufferAttr); + } else { + ret = EffectChainManagerCreate(u->sink->name, u->bufferAttr); + } + if (ret != 0) { + return -1; + } + + int32_t bitSize = pa_sample_size_of_format(u->sampleSpec.format); + size_t targetSize = u->sampleSpec.channels * frameLen * bitSize; + u->bufInQ = pa_memblockq_new("module-effect-sink bufInQ", 0, MEMBLOCKQ_MAXLENGTH, targetSize, &u->sampleSpec, + 1, 1, 0, NULL); + return 0; +} + +int pa__init(pa_module *m) +{ struct userdata *u; pa_modargs *ma; pa_sink *masterSink; @@ -649,47 +704,23 @@ int pa__init(pa_module *m) AUDIO_ERR_LOG("MasterSink not found"); return InitFail(m, ma); } - + u = pa_xnew0(struct userdata, 1); u->module = m; m->userdata = u; - ret = CreateSink(m, ma, masterSink, u); - if (ret != 0) { - return ret; - } - - ret = CreateSinkInput(m, ma, masterSink, u); - if (ret != 0) { - return ret; + if (CreateSink(m, ma, masterSink, u) != 0) { + return InitFail(m, ma); } - // Set buffer attributes - int32_t frameLen = EffectChainManagerGetFrameLen(); - u->format = u->sampleSpec.format; - u->processLen = u->sampleSpec.channels * frameLen; - u->processSize = u->processLen * sizeof(float); - - u->bufferAttr = pa_xnew0(BufferAttr, 1); - pa_assert_se(u->bufferAttr->bufIn = (float *)malloc(u->processSize)); - pa_assert_se(u->bufferAttr->bufOut = (float *)malloc(u->processSize)); - u->bufferAttr->samplingRate = u->sampleSpec.rate; - u->bufferAttr->frameLen = frameLen; - u->bufferAttr->numChanIn = u->sampleSpec.channels; - u->bufferAttr->numChanOut = u->sampleSpec.channels; - if (EffectChainManagerCreate(u->sink->name, u->bufferAttr) != 0) { + if (CreateSinkInput(m, ma, masterSink, u) != 0 || ConfigSinkInput(u) != 0) { return InitFail(m, ma); } - int32_t bitSize = pa_sample_size_of_format(u->sampleSpec.format); - size_t targetSize = u->sampleSpec.channels * frameLen * bitSize; - u->bufInQ = pa_memblockq_new("module-effect-sink bufInQ", 0, MEMBLOCKQ_MAXLENGTH, targetSize, &u->sampleSpec, - 1, 1, 0, NULL); - pa_sink_input_put(u->sinkInput); pa_sink_put(u->sink); pa_sink_input_cork(u->sinkInput, false); - + pa_modargs_free(ma); return 0; } diff --git a/frameworks/native/pulseaudio/modules/hdi/hdi_sink.c b/frameworks/native/pulseaudio/modules/hdi/hdi_sink.c index ecd4ee166aaa67f3815dede5e88157177066b134..e70d84c115161c3601328ae6998ab197035c8ba1 100644 --- a/frameworks/native/pulseaudio/modules/hdi/hdi_sink.c +++ b/frameworks/native/pulseaudio/modules/hdi/hdi_sink.c @@ -361,7 +361,7 @@ static void SinkUpdateRequestedLatencyCb(pa_sink *s) static int SinkProcessMsg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) { - AUDIO_INFO_LOG("SinkProcessMsg: code: %{public}d", code); + AUDIO_DEBUG_LOG("SinkProcessMsg: code: %{public}d", code); struct Userdata *u = PA_SINK(o)->userdata; pa_assert(u); diff --git a/interfaces/inner_api/native/audiocommon/include/audio_info.h b/interfaces/inner_api/native/audiocommon/include/audio_info.h index 7578ed5526601781f42775a0af339df2c70181f9..80992723dbb50a8c6e9574164b3db1820d2af3e6 100644 --- a/interfaces/inner_api/native/audiocommon/include/audio_info.h +++ b/interfaces/inner_api/native/audiocommon/include/audio_info.h @@ -527,10 +527,19 @@ enum SourceType { SOURCE_TYPE_INVALID = -1, SOURCE_TYPE_MIC, SOURCE_TYPE_VOICE_RECOGNITION = 1, + SOURCE_TYPE_PLAYBACK_CAPTURE = 2, SOURCE_TYPE_VOICE_COMMUNICATION = 7, SOURCE_TYPE_ULTRASONIC = 8 }; +/** + * Enumerates audio stream privacy type for playback capture. + */ +enum AudioPrivacyType { + PRIVACY_TYPE_PUBLIC = 0, + PRIVACY_TYPE_PRIVATE = 1 +}; + /** * Enumerates the renderer playback speed. */ @@ -684,6 +693,7 @@ struct AudioRendererDesc { struct AudioRendererOptions { AudioStreamInfo streamInfo; AudioRendererInfo rendererInfo; + AudioPrivacyType privacyType = PRIVACY_TYPE_PUBLIC; }; struct MicStateChangeEvent { @@ -721,9 +731,18 @@ enum AudioScene { AUDIO_SCENE_PHONE_CHAT, }; +struct CaptureFilterOptions { + StreamUsage usage; +}; + +struct AudioPlaybackCaptureConfig { + std::vector filterOptions; +}; + struct AudioCapturerOptions { AudioStreamInfo streamInfo; AudioCapturerInfo capturerInfo; + AudioPlaybackCaptureConfig playbackCaptureConfig; }; struct AppInfo { @@ -778,6 +797,30 @@ const std::vector AUDIO_SUPPORTED_SAMPLING_RATES { SAMPLE_RATE_96000 }; +const std::vector AUDIO_SUPPORTED_STREAM_USAGES { + STREAM_USAGE_UNKNOWN, + STREAM_USAGE_MEDIA, + STREAM_USAGE_MUSIC, + STREAM_USAGE_VOICE_COMMUNICATION, + STREAM_USAGE_VOICE_ASSISTANT, + STREAM_USAGE_ALARM, + STREAM_USAGE_VOICE_MESSAGE, + STREAM_USAGE_NOTIFICATION_RINGTONE, + STREAM_USAGE_RINGTONE, + STREAM_USAGE_NOTIFICATION, + STREAM_USAGE_ACCESSIBILITY, + STREAM_USAGE_SYSTEM, + STREAM_USAGE_MOVIE, + STREAM_USAGE_GAME, + STREAM_USAGE_AUDIOBOOK, + STREAM_USAGE_NAVIGATION, + STREAM_USAGE_DTMF, + STREAM_USAGE_ENFORCED_TONE, + STREAM_USAGE_ULTRASONIC, + STREAM_USAGE_RANGING, + STREAM_USAGE_VOICE_MODEM_COMMUNICATION +}; + struct BufferDesc { uint8_t* buffer; size_t bufLength; diff --git a/interfaces/inner_api/native/audiorenderer/include/audio_renderer.h b/interfaces/inner_api/native/audiorenderer/include/audio_renderer.h index 8e3bf15dd886a1b51f8772ae64df7f7eebc1194a..8a4f620ee77d5c18ba5fdcc035d0c3342da92284 100644 --- a/interfaces/inner_api/native/audiorenderer/include/audio_renderer.h +++ b/interfaces/inner_api/native/audiorenderer/include/audio_renderer.h @@ -193,6 +193,15 @@ public: static std::unique_ptr Create(const std::string cachePath, const AudioRendererOptions &rendererOptions, const AppInfo &appInfo); + /** + * @brief Sets audio privacy type. + * + * @param privacyType Indicates information about audio privacy type. For details, see + * {@link AudioPrivacyType}. + * @since 10 + */ + virtual void SetAudioPrivacyType(AudioPrivacyType privacyType) = 0; + /** * @brief Sets audio renderer parameters. * diff --git a/interfaces/kits/c/common/native_audiostream_base.h b/interfaces/kits/c/common/native_audiostream_base.h index 49ed115176b5beae71cf432bd5814abe1221d2bc..eee4a1eed516a55752c624d0864135f0ea829266 100644 --- a/interfaces/kits/c/common/native_audiostream_base.h +++ b/interfaces/kits/c/common/native_audiostream_base.h @@ -168,6 +168,7 @@ typedef enum { AUDIOSTREAM_SOURCE_TYPE_INVALID = -1, AUDIOSTREAM_SOURCE_TYPE_MIC, AUDIOSTREAM_SOURCE_TYPE_VOICE_RECOGNITION = 1, + AUDIOSTREAM_SOURCE_TYPE_PLAYBACK_CAPTURE = 2, AUDIOSTREAM_SOURCE_TYPE_VOICE_COMMUNICATION = 7 } OH_AudioStream_SourceType; diff --git a/interfaces/kits/js/audio_capturer/include/audio_capturer_napi.h b/interfaces/kits/js/audio_capturer/include/audio_capturer_napi.h index 8e55295928d836cf2d507df81b81a03a4b043090..f425a7d6ca0d35517a101907e6f2c96cee1db48b 100644 --- a/interfaces/kits/js/audio_capturer/include/audio_capturer_napi.h +++ b/interfaces/kits/js/audio_capturer/include/audio_capturer_napi.h @@ -109,6 +109,8 @@ private: static void CheckCapturerAsyncCallbackComplete(napi_env env, napi_status status, void *data); static napi_status CreateReadAsyncWork(const AudioCapturerAsyncContext &asyncContext); static napi_status AddNamedProperty(napi_env env, napi_value object, const std::string name, int32_t enumValue); + static bool ParseCaptureFilterOptionsVector(napi_env env, napi_value root, std::vector &filterOptions); + static bool ParsePlaybackCaptureConfig(napi_env env, napi_value root, AudioPlaybackCaptureConfig* captureConfig); static bool ParseCapturerOptions(napi_env env, napi_value root, AudioCapturerOptions *opts); static bool ParseCapturerInfo(napi_env env, napi_value root, AudioCapturerInfo *capturerInfo); static bool ParseStreamInfo(napi_env env, napi_value root, AudioStreamInfo* streamInfo); diff --git a/interfaces/kits/js/audio_capturer/include/audio_parameters_napi.h b/interfaces/kits/js/audio_capturer/include/audio_parameters_napi.h index dc9484d9a439bdf4b9607ee1d6f0c0d9b9207f57..8cb63918f6622db522ce98a55ccec0f8dd3945dc 100644 --- a/interfaces/kits/js/audio_capturer/include/audio_parameters_napi.h +++ b/interfaces/kits/js/audio_capturer/include/audio_parameters_napi.h @@ -100,6 +100,7 @@ static const std::map sourceTypeMap = { {"SOURCE_TYPE_INVALID", SOURCE_TYPE_INVALID}, {"SOURCE_TYPE_MIC", SOURCE_TYPE_MIC}, {"SOURCE_TYPE_VOICE_RECOGNITION", SOURCE_TYPE_VOICE_RECOGNITION}, + {"SOURCE_TYPE_PLAYBACK_CAPTURE", SOURCE_TYPE_PLAYBACK_CAPTURE}, {"SOURCE_TYPE_VOICE_COMMUNICATION", SOURCE_TYPE_VOICE_COMMUNICATION} }; diff --git a/interfaces/kits/js/audio_renderer/include/audio_renderer_napi.h b/interfaces/kits/js/audio_renderer/include/audio_renderer_napi.h index 3ee51393aa7e6fa52e8263c97b2fa5382ac476f8..edae1fa53186db757c21a85d411e10c3e585befd 100644 --- a/interfaces/kits/js/audio_renderer/include/audio_renderer_napi.h +++ b/interfaces/kits/js/audio_renderer/include/audio_renderer_napi.h @@ -182,6 +182,7 @@ private: static napi_value CreateAudioStateObject(napi_env env); static napi_value CreateAudioSampleFormatObject(napi_env env); static napi_value CreateAudioEffectModeObject(napi_env env); + static napi_value CreateAudioPrivacyTypeObject(napi_env env); static void RegisterRendererDeviceChangeCallback(napi_env env, napi_value* args, AudioRendererNapi *rendererNapi); static void UnregisterRendererDeviceChangeCallback(napi_env env, size_t argc, napi_value* args, AudioRendererNapi *rendererNapi); @@ -192,6 +193,7 @@ private: static napi_ref audioState_; static napi_ref sampleFormat_; static napi_ref audioEffectMode_; + static napi_ref audioPrivacyType_; static std::unique_ptr sAudioParameters_; static std::unique_ptr sRendererOptions_; static std::mutex createMutex_; @@ -245,6 +247,11 @@ static const std::map audioStateMap = { {"STATE_RELEASED", RENDERER_RELEASED}, {"STATE_PAUSED", RENDERER_PAUSED} }; + +static const std::map audioPrivacyTypeMap = { + {"PRIVACY_TYPE_PUBLIC", PRIVACY_TYPE_PUBLIC}, + {"PRIVACY_TYPE_PRIVATE", PRIVACY_TYPE_PRIVATE} +}; } // namespace AudioStandard } // namespace OHOS #endif /* AUDIO_RENDERER_NAPI_H_ */ diff --git a/services/audio_policy/client/include/audio_policy_base.h b/services/audio_policy/client/include/audio_policy_base.h index ced5416b059b42ee0cd70a3b1c6130f6b59b0b45..40541756fb317e91c25fc887d0b85952cbc6e17f 100644 --- a/services/audio_policy/client/include/audio_policy_base.h +++ b/services/audio_policy/client/include/audio_policy_base.h @@ -203,6 +203,9 @@ public: virtual float GetSystemVolumeInDb(AudioVolumeType volumeType, int32_t volumeLevel, DeviceType deviceType) = 0; virtual int32_t QueryEffectSceneMode(SupportedEffectConfig &supportedEffectConfig) = 0; + + virtual int32_t SetPlaybackCapturerFilterInfos(std::vector filterOptions) = 0; + public: DECLARE_INTERFACE_DESCRIPTOR(u"IAudioPolicy"); }; diff --git a/services/audio_policy/client/include/audio_policy_manager_stub.h b/services/audio_policy/client/include/audio_policy_manager_stub.h index 3f350cf1fe86527022a813154253349a1b8683f1..d9cfa4090cdaa50da0403f008dfa1cd65ea32d71 100644 --- a/services/audio_policy/client/include/audio_policy_manager_stub.h +++ b/services/audio_policy/client/include/audio_policy_manager_stub.h @@ -111,6 +111,7 @@ private: void GetSystemVolumeInDbInternal(MessageParcel &data, MessageParcel &reply); void IsVolumeUnadjustableInternal(MessageParcel &data, MessageParcel &reply); void QueryEffectSceneModeInternal(MessageParcel &data, MessageParcel &reply); + void SetPlaybackCapturerFilterInfosInternal(MessageParcel &data, MessageParcel &reply); }; } // namespace AudioStandard } // namespace OHOS diff --git a/services/audio_policy/client/include/audio_policy_proxy.h b/services/audio_policy/client/include/audio_policy_proxy.h index 7c6811eebb2a15e43b0be855c558c36f4d4d2407..0b11e29c0ef7399d60259567fb18ab0b19779a76 100644 --- a/services/audio_policy/client/include/audio_policy_proxy.h +++ b/services/audio_policy/client/include/audio_policy_proxy.h @@ -196,6 +196,8 @@ public: float GetSystemVolumeInDb(AudioVolumeType volumeType, int32_t volumeLevel, DeviceType deviceType) override; int32_t QueryEffectSceneMode(SupportedEffectConfig &supportedEffectConfig) override; + + int32_t SetPlaybackCapturerFilterInfos(std::vector filterOptions) override; private: static inline BrokerDelegator mDdelegator; void WriteAudioInteruptParams(MessageParcel &parcel, const AudioInterrupt &audioInterrupt); diff --git a/services/audio_policy/client/src/audio_policy_manager.cpp b/services/audio_policy/client/src/audio_policy_manager.cpp index ebffaa542c6497884676df66e6f4e2f033255ef3..34fcaeef8fa38230ec7df4f0daeca740555a041e 100644 --- a/services/audio_policy/client/src/audio_policy_manager.cpp +++ b/services/audio_policy/client/src/audio_policy_manager.cpp @@ -1144,5 +1144,17 @@ int32_t AudioPolicyManager::QueryEffectSceneMode(SupportedEffectConfig &supporte int error = gsp->QueryEffectSceneMode(supportedEffectConfig); return error; } + +int32_t AudioPolicyManager::SetPlaybackCapturerFilterInfos(std::vector filterOptions) +{ + AUDIO_INFO_LOG("AudioPolicyManager::SetPlaybackCapturerFilterInfos"); + const sptr gsp = GetAudioPolicyManagerProxy(); + if (gsp == nullptr) { + AUDIO_ERR_LOG("SetPlaybackCapturerFilterInfos: audio policy manager proxy is NULL."); + return ERROR; + } + + return gsp->SetPlaybackCapturerFilterInfos(filterOptions); +} } // namespace AudioStandard } // namespace OHOS diff --git a/services/audio_policy/client/src/audio_policy_proxy.cpp b/services/audio_policy/client/src/audio_policy_proxy.cpp index 942ac8c9200a6a743b91057b6c5e68f0ff7e3e94..fd5466bead47933bb61a5aac453fc661e6b7b49d 100644 --- a/services/audio_policy/client/src/audio_policy_proxy.cpp +++ b/services/audio_policy/client/src/audio_policy_proxy.cpp @@ -1892,7 +1892,7 @@ int32_t AudioPolicyProxy::SetSystemSoundUri(const std::string &key, const std::s MessageOption option; if (!data.WriteInterfaceToken(GetDescriptor())) { - AUDIO_ERR_LOG("IsAudioRendererLowLatencySupported WriteInterfaceToken failed"); + AUDIO_ERR_LOG("SetSystemSoundUri WriteInterfaceToken failed"); return IPC_PROXY_ERR; } data.WriteString(key); @@ -1913,7 +1913,7 @@ std::string AudioPolicyProxy::GetSystemSoundUri(const std::string &key) MessageOption option; if (!data.WriteInterfaceToken(GetDescriptor())) { - AUDIO_ERR_LOG("IsAudioRendererLowLatencySupported WriteInterfaceToken failed"); + AUDIO_ERR_LOG("GetSystemSoundUri WriteInterfaceToken failed"); return ""; } data.WriteString(key); @@ -2099,5 +2099,30 @@ int32_t AudioPolicyProxy::QueryEffectSceneMode(SupportedEffectConfig &supportedE } return 0; } + +int32_t AudioPolicyProxy::SetPlaybackCapturerFilterInfos(std::vector filterOptions) +{ + MessageParcel data; + MessageParcel reply; + MessageOption option; + + if (!data.WriteInterfaceToken(GetDescriptor())) { + AUDIO_ERR_LOG(" SetPlaybackCapturerFilterInfos WriteInterfaceToken failed"); + return ERROR; + } + size_t ss = filterOptions.size(); + data.WriteInt32(ss); + for (size_t i = 0; i < ss; i++) { + data.WriteInt32(filterOptions[i].usage); + } + int32_t error = Remote()->SendRequest( + static_cast(AudioPolicyInterfaceCode::SET_PLAYBACK_CAPTURER_FILTER_INFO), data, reply, option); + if (error != ERR_NONE) { + AUDIO_ERR_LOG("SetPlaybackCapturerFilterInfos failed, error: %d", error); + return ERROR; + } + return reply.ReadInt32(); +} + } // namespace AudioStandard } // namespace OHOS diff --git a/services/audio_policy/common/include/audio_policy_ipc_interface_code.h b/services/audio_policy/common/include/audio_policy_ipc_interface_code.h index a88d2bcb6c9a26701490c9686ddd93672ea37dea..8ad02d7f82cc9b84310205f727e9062f174113c0 100644 --- a/services/audio_policy/common/include/audio_policy_ipc_interface_code.h +++ b/services/audio_policy/common/include/audio_policy_ipc_interface_code.h @@ -99,6 +99,7 @@ enum class AudioPolicyInterfaceCode { ADJUST_SYSTEM_VOLUME_BY_STEP, GET_SYSTEM_VOLUME_IN_DB, QUERY_EFFECT_SCENEMODE, + SET_PLAYBACK_CAPTURER_FILTER_INFO, }; } // namespace AudioStandard } // namespace OHOS diff --git a/services/audio_policy/server/config/audio_interrupt_policy_config.xml b/services/audio_policy/server/config/audio_interrupt_policy_config.xml index 65f89fa3ea2e39ece7f6205b69a76cd47cb5788b..d98cac0b6d0ce10c016224a9a10733831a0e1959 100644 --- a/services/audio_policy/server/config/audio_interrupt_policy_config.xml +++ b/services/audio_policy/server/config/audio_interrupt_policy_config.xml @@ -38,6 +38,7 @@ + nil @@ -51,6 +52,7 @@ + @@ -90,6 +92,7 @@ + @@ -114,6 +117,7 @@ + @@ -141,6 +145,7 @@ + @@ -170,6 +175,7 @@ + @@ -197,6 +203,7 @@ + @@ -223,6 +230,7 @@ + @@ -245,6 +253,7 @@ + @@ -285,6 +294,7 @@ + nil @@ -298,6 +308,7 @@ + @@ -330,6 +341,7 @@ + @@ -365,6 +377,7 @@ + @@ -394,6 +407,7 @@ + nil @@ -420,6 +434,7 @@ + @@ -448,6 +463,7 @@ + @@ -477,6 +493,7 @@ + nil @@ -504,6 +521,34 @@ + + + nil + + + + + nil + + + + + + + + + + + + + + + + + + + + nil diff --git a/services/audio_policy/server/include/audio_policy_server.h b/services/audio_policy/server/include/audio_policy_server.h index fbfaf8bfc377036e862dcc4d472971a1ec31e5e5..8325e8b42cd7daf0c7f7b838c53228f66075b42d 100644 --- a/services/audio_policy/server/include/audio_policy_server.h +++ b/services/audio_policy/server/include/audio_policy_server.h @@ -179,6 +179,8 @@ public: void OnSessionRemoved(const uint32_t sessionID) override; + void OnPlaybackCapturerStop() override; + int32_t Dump(int32_t fd, const std::vector &args) override; bool VerifyClientMicrophonePermission(uint32_t appTokenId, int32_t appUid, bool privacyFlag, @@ -250,6 +252,8 @@ public: int32_t QueryEffectSceneMode(SupportedEffectConfig &supportedEffectConfig) override; + int32_t SetPlaybackCapturerFilterInfos(std::vector options) override; + class RemoteParameterCallback : public AudioParameterCallback { public: RemoteParameterCallback(sptr server); diff --git a/services/audio_policy/server/include/service/audio_policy_service.h b/services/audio_policy/server/include/service/audio_policy_service.h index aadfa0ddf1f88334897b0647762c260a3ac89e53..07412b39c07f7a6f8f28b4bc1159ce4780fef8c6 100644 --- a/services/audio_policy/server/include/service/audio_policy_service.h +++ b/services/audio_policy/server/include/service/audio_policy_service.h @@ -258,6 +258,10 @@ public: void SetRemoteDisplayName(const std::string &deviceName); + int32_t SetPlaybackCapturerFilterInfos(std::vector options); + + void UnloadLoopback(); + private: AudioPolicyService() :audioPolicyManager_(AudioPolicyManagerFactory::GetAudioPolicyManager()), @@ -386,7 +390,15 @@ private: void UpdateEffectDefaultSink(DeviceType deviceType); void LoadEffectSinks(); - + + void LoadSinksForCapturer(); + + void LoadInnerCapturerSink(); + + void LoadReceiverSink(); + + void LoadLoopback(); + DeviceType FindConnectedHeadset(); bool interruptEnabled_ = true; diff --git a/services/audio_policy/server/include/service/common/audio_module_info.h b/services/audio_policy/server/include/service/common/audio_module_info.h index 5891e4c41c1d96a114866885f651c3acde3e5119..5ddef4b6505cd566a15f4f3d67377ede7fa57ab0 100644 --- a/services/audio_policy/server/include/service/common/audio_module_info.h +++ b/services/audio_policy/server/include/service/common/audio_module_info.h @@ -89,8 +89,20 @@ public: std::string fileName; std::string networkId; std::string deviceType; + std::string sceneName; std::list ports; }; + +class LoopbackModuleInfo { +public: + LoopbackModuleInfo() = default; + virtual ~LoopbackModuleInfo() = default; + + std::string lib; + std::string sink; + std::string source; +}; + } // namespace AudioStandard } // namespace OHOS diff --git a/services/audio_policy/server/include/service/common/audio_session_callback.h b/services/audio_policy/server/include/service/common/audio_session_callback.h index 4a34eb169b8c16593a1b894cde9af982c6fbd28f..b4bb6824b2c4bd59de9ff24cc45963a4c0b20e6d 100644 --- a/services/audio_policy/server/include/service/common/audio_session_callback.h +++ b/services/audio_policy/server/include/service/common/audio_session_callback.h @@ -26,6 +26,8 @@ public: virtual ~AudioSessionCallback() = default; virtual void OnSessionRemoved(const uint32_t sessionID) = 0; + + virtual void OnPlaybackCapturerStop() = 0; }; } // namespce AudioStandard } // namespace OHOS diff --git a/services/audio_policy/server/include/service/interface/iaudio_policy_interface.h b/services/audio_policy/server/include/service/interface/iaudio_policy_interface.h index d403eac0d66a30d984d7afb5dd7d52c4e3ba4c46..8dea4cc3d916580f4620742d6f42b294ade9c44e 100644 --- a/services/audio_policy/server/include/service/interface/iaudio_policy_interface.h +++ b/services/audio_policy/server/include/service/interface/iaudio_policy_interface.h @@ -64,6 +64,8 @@ public: virtual AudioIOHandle OpenAudioPort(const AudioModuleInfo &audioPortInfo) = 0; + virtual AudioIOHandle LoadLoopback(const LoopbackModuleInfo &moduleInfo) = 0; + virtual int32_t CloseAudioPort(AudioIOHandle ioHandle) = 0; virtual int32_t SelectDevice(DeviceRole deviceRole, InternalDeviceType deviceType, std::string name); diff --git a/services/audio_policy/server/include/service/manager/audio_adapter_manager.h b/services/audio_policy/server/include/service/manager/audio_adapter_manager.h index aad3c8da002a0fab2178ba6cfe289ca731ed69d8..fa06852902fddc730c58dd01c6b8f99f56f6238b 100644 --- a/services/audio_policy/server/include/service/manager/audio_adapter_manager.h +++ b/services/audio_policy/server/include/service/manager/audio_adapter_manager.h @@ -38,6 +38,8 @@ public: static constexpr std::string_view PIPE_SOURCE = "libmodule-pipe-source.z.so"; static constexpr std::string_view CLUSTER_SINK = "libmodule-cluster-sink.z.so"; static constexpr std::string_view EFFECT_SINK = "libmodule-effect-sink.z.so"; + static constexpr std::string_view INNER_CAPTURER_SINK = "libmodule-inner-capturer-sink.z.so"; + static constexpr std::string_view RECEIVER_SINK = "libmodule-receiver-sink.z.so"; static constexpr uint32_t KVSTORE_CONNECT_RETRY_COUNT = 5; static constexpr uint32_t KVSTORE_CONNECT_RETRY_DELAY_TIME = 200000; static constexpr float MIN_VOLUME = 0.0f; @@ -81,6 +83,8 @@ public: AudioIOHandle OpenAudioPort(const AudioModuleInfo &audioModuleInfo); + AudioIOHandle LoadLoopback(const LoopbackModuleInfo &moduleInfo); + int32_t CloseAudioPort(AudioIOHandle ioHandle); int32_t SelectDevice(DeviceRole deviceRole, InternalDeviceType deviceType, std::string name); @@ -173,6 +177,7 @@ private: } std::string GetModuleArgs(const AudioModuleInfo &audioModuleInfo) const; + std::string GetLoopbackModuleArgs(const LoopbackModuleInfo &moduleInfo) const; AudioStreamType GetStreamIDByType(std::string streamType); AudioStreamType GetStreamForVolumeMap(AudioStreamType streamType); bool InitAudioPolicyKvStore(bool& isFirstBoot); @@ -278,6 +283,16 @@ public: audioAdapterManager_->sessionCallback_->OnSessionRemoved(sessionID); } } + + void OnPlaybackCapturerStop() + { + AUDIO_INFO_LOG("PolicyCallbackImpl OnPlaybackCapturerStop"); + if (audioAdapterManager_->sessionCallback_ == nullptr) { + AUDIO_DEBUG_LOG("PolicyCallbackImpl sessionCallback_ nullptr"); + } else { + audioAdapterManager_->sessionCallback_->OnPlaybackCapturerStop(); + } + } private: std::shared_ptr audioAdapterManager_; }; diff --git a/services/audio_policy/server/src/audio_policy_manager_stub.cpp b/services/audio_policy/server/src/audio_policy_manager_stub.cpp index 36603b5690c723310a79ec318106b9762ef1b878..f3df1064908e85701c09371d898f8258207b1072 100644 --- a/services/audio_policy/server/src/audio_policy_manager_stub.cpp +++ b/services/audio_policy/server/src/audio_policy_manager_stub.cpp @@ -990,6 +990,20 @@ void AudioPolicyManagerStub::QueryEffectSceneModeInternal(MessageParcel &data, M } } +void AudioPolicyManagerStub::SetPlaybackCapturerFilterInfosInternal(MessageParcel &data, MessageParcel &reply) +{ + std::vector filterInfo; + int32_t ss = data.ReadInt32(); + for (int32_t i = 0; i < ss; i++) { + CaptureFilterOptions info; + info.usage = static_cast(data.ReadInt32()); + filterInfo.push_back(info); + } + + int32_t ret = SetPlaybackCapturerFilterInfos(filterInfo); + reply.WriteInt32(ret); +} + int AudioPolicyManagerStub::OnRemoteRequest( uint32_t code, MessageParcel &data, MessageParcel &reply, MessageOption &option) { @@ -1305,6 +1319,10 @@ int AudioPolicyManagerStub::OnRemoteRequest( GetSystemVolumeInDbInternal(data, reply); break; + case static_cast(AudioPolicyInterfaceCode::SET_PLAYBACK_CAPTURER_FILTER_INFO): + SetPlaybackCapturerFilterInfosInternal(data, reply); + break; + default: AUDIO_ERR_LOG("default case, need check AudioPolicyManagerStub"); return IPCObjectStub::OnRemoteRequest(code, data, reply, option); diff --git a/services/audio_policy/server/src/audio_policy_server.cpp b/services/audio_policy/server/src/audio_policy_server.cpp index 350f7259fd20bfb24512cf1f6edf102ca05cc249..186989e007b655031688a1d7ae8c2bb17cbdf6e4 100644 --- a/services/audio_policy/server/src/audio_policy_server.cpp +++ b/services/audio_policy/server/src/audio_policy_server.cpp @@ -1106,6 +1106,7 @@ int32_t AudioPolicyServer::ProcessFocusEntry(const AudioInterrupt &incomingInter } std::pair audioFocusTypePair = std::make_pair((iterActive->first).audioFocusType, incomingFocusType); + CHECK_AND_RETURN_RET_LOG(focusMap.find(audioFocusTypePair) != focusMap.end(), ERR_INVALID_PARAM, "ProcessFocusEntry: audio focus type pair is invalid"); AudioFocusEntry focusEntry = focusMap[audioFocusTypePair]; @@ -1421,6 +1422,11 @@ void AudioPolicyServer::OnSessionRemoved(const uint32_t sessionID) (void)UnsetAudioInterruptCallback(removedSessionID); } +void AudioPolicyServer::OnPlaybackCapturerStop() +{ + mPolicyService.UnloadLoopback(); +} + AudioStreamType AudioPolicyServer::GetStreamInFocus() { AudioStreamType streamInFocus = STREAM_DEFAULT; @@ -2199,5 +2205,10 @@ int32_t AudioPolicyServer::QueryEffectSceneMode(SupportedEffectConfig &supported int32_t ret = mPolicyService.QueryEffectManagerSceneMode(supportedEffectConfig); return ret; } + +int32_t AudioPolicyServer::SetPlaybackCapturerFilterInfos(std::vector options) +{ + return mPolicyService.SetPlaybackCapturerFilterInfos(options); +} } // namespace AudioStandard } // namespace OHOS diff --git a/services/audio_policy/server/src/service/audio_policy_service.cpp b/services/audio_policy/server/src/service/audio_policy_service.cpp index 0d7d2a75737173f4b37721ee0e5d1eee5f6e6212..79321933a918dba06f0574824c6da5062accf7cf 100644 --- a/services/audio_policy/server/src/service/audio_policy_service.cpp +++ b/services/audio_policy/server/src/service/audio_policy_service.cpp @@ -39,6 +39,11 @@ namespace OHOS { namespace AudioStandard { using namespace std; +static const std::string INNER_CAPTURER_SINK_NAME = "InnerCapturer"; +static const std::string RECEIVER_SINK_NAME = "Receiver"; +static const std::string SINK_NAME_FOR_CAPTURE_SUFFIX = "_CAP"; +static const std::string MONITOR_SOURCE_SUFFIX = ".monitor"; + const uint32_t PCM_8_BIT = 8; const uint32_t PCM_16_BIT = 16; const uint32_t PCM_24_BIT = 24; @@ -2012,6 +2017,99 @@ void AudioPolicyService::UpdateEffectDefaultSink(DeviceType deviceType) } } +void AudioPolicyService::LoadSinksForCapturer() +{ + AUDIO_INFO_LOG("LoadSinksForCapturer"); + LoadInnerCapturerSink(); + LoadReceiverSink(); + const sptr gsp = GetAudioServerProxy(); + if (gsp == nullptr) { + AUDIO_ERR_LOG("LoadSinksForCapturer error for g_adProxy null"); + return; + } + bool ret = gsp->CreatePlaybackCapturerManager(); + CHECK_AND_RETURN_LOG(ret, "PlaybackCapturerManager create failed"); +} + +void AudioPolicyService::LoadInnerCapturerSink() +{ + AUDIO_INFO_LOG("LoadInnerCapturerSink"); + AudioModuleInfo moduleInfo = {}; + moduleInfo.lib = "libmodule-inner-capturer-sink.z.so"; + moduleInfo.name = INNER_CAPTURER_SINK_NAME; + AudioIOHandle ioHandle = audioPolicyManager_.OpenAudioPort(moduleInfo); + CHECK_AND_RETURN_LOG(ioHandle != OPEN_PORT_FAILURE, + "OpenAudioPort failed %{public}d for InnerCapturer sink", ioHandle); + IOHandles_[moduleInfo.name] = ioHandle; +} + +void AudioPolicyService::LoadReceiverSink() +{ + AUDIO_INFO_LOG("LoadReceiverSink"); + AudioModuleInfo moduleInfo = {}; + moduleInfo.name = RECEIVER_SINK_NAME; + moduleInfo.lib = "libmodule-receiver-sink.z.so"; + AudioIOHandle ioHandle = audioPolicyManager_.OpenAudioPort(moduleInfo); + CHECK_AND_RETURN_LOG(ioHandle != OPEN_PORT_FAILURE, "OpenAudioPort failed %{public}d for Receiver sink", ioHandle); + IOHandles_[moduleInfo.name] = ioHandle; +} + +void AudioPolicyService::LoadLoopback() +{ + AudioIOHandle ioHandle; + std::string moduleName; + AUDIO_INFO_LOG("LoadLoopback"); + + if (IOHandles_.count(INNER_CAPTURER_SINK_NAME) != 1u) { + AUDIO_ERR_LOG("LoadLoopback failed for InnerCapturer not loaded"); + return; + } + + LoopbackModuleInfo moduleInfo = {}; + moduleInfo.lib = "libmodule-loopback.z.so"; + moduleInfo.sink = INNER_CAPTURER_SINK_NAME; + + for (auto sceneType = AUDIO_SUPPORTED_SCENE_TYPES.begin(); sceneType != AUDIO_SUPPORTED_SCENE_TYPES.end(); + ++sceneType) { + moduleInfo.source = sceneType->second + SINK_NAME_FOR_CAPTURE_SUFFIX + MONITOR_SOURCE_SUFFIX; + ioHandle = audioPolicyManager_.LoadLoopback(moduleInfo); + CHECK_AND_RETURN_LOG(ioHandle != OPEN_PORT_FAILURE, "LoadLoopback failed %{public}d", ioHandle); + moduleName = moduleInfo.source + moduleInfo.sink; + IOHandles_[moduleName] = ioHandle; + } + + if (IOHandles_.count(RECEIVER_SINK_NAME) != 1u) { + AUDIO_ERR_LOG("receiver sink not exist"); + } else { + moduleInfo.source = RECEIVER_SINK_NAME + MONITOR_SOURCE_SUFFIX; + ioHandle = audioPolicyManager_.LoadLoopback(moduleInfo); + CHECK_AND_RETURN_LOG(ioHandle != OPEN_PORT_FAILURE, "LoadLoopback failed %{public}d", ioHandle); + moduleName = moduleInfo.source + moduleInfo.sink; + IOHandles_[moduleName] = ioHandle; + } +} + +void AudioPolicyService::UnloadLoopback() +{ + std::string module; + AUDIO_INFO_LOG("UnloadLoopback"); + + for (auto sceneType = AUDIO_SUPPORTED_SCENE_TYPES.begin(); sceneType != AUDIO_SUPPORTED_SCENE_TYPES.end(); + ++sceneType) { + module = sceneType->second + SINK_NAME_FOR_CAPTURE_SUFFIX + MONITOR_SOURCE_SUFFIX + INNER_CAPTURER_SINK_NAME; + if (IOHandles_.find(module) != IOHandles_.end()) { + audioPolicyManager_.CloseAudioPort(IOHandles_[module]); + IOHandles_.erase(module); + } + } + + module = RECEIVER_SINK_NAME + MONITOR_SOURCE_SUFFIX + INNER_CAPTURER_SINK_NAME; + if (IOHandles_.find(module) != IOHandles_.end()) { + audioPolicyManager_.CloseAudioPort(IOHandles_[module]); + IOHandles_.erase(module); + } +} + void AudioPolicyService::LoadEffectSinks() { // Create sink for each effect @@ -2035,10 +2133,19 @@ void AudioPolicyService::LoadEffectSinks() ++sceneType) { AUDIO_INFO_LOG("Initial sink for scene name %{public}s", sceneType->second.c_str()); moduleInfo.name = sceneType->second; + moduleInfo.sceneName.clear(); + ioHandle = audioPolicyManager_.OpenAudioPort(moduleInfo); + CHECK_AND_RETURN_LOG(ioHandle != OPEN_PORT_FAILURE, "OpenAudioPort failed %{public}d", ioHandle); + IOHandles_[moduleInfo.name] = ioHandle; + + moduleInfo.name += SINK_NAME_FOR_CAPTURE_SUFFIX; + moduleInfo.sceneName = sceneType->second; + AUDIO_INFO_LOG("Initial effect sink:%{public}s for capturer", moduleInfo.name.c_str()); ioHandle = audioPolicyManager_.OpenAudioPort(moduleInfo); CHECK_AND_RETURN_LOG(ioHandle != OPEN_PORT_FAILURE, "OpenAudioPort failed %{public}d", ioHandle); IOHandles_[moduleInfo.name] = ioHandle; } + LoadSinksForCapturer(); // repy on the success of loading effect sink } void AudioPolicyService::LoadEffectLibrary() @@ -3059,5 +3166,26 @@ int32_t AudioPolicyService::QueryEffectManagerSceneMode(SupportedEffectConfig& s int32_t ret = audioEffectManager_.QueryEffectManagerSceneMode(supportedEffectConfig); return ret; } + +int32_t AudioPolicyService::SetPlaybackCapturerFilterInfos(std::vector options) +{ + LoadLoopback(); + const sptr gsp = GetAudioServerProxy(); + if (gsp == nullptr) { + AUDIO_ERR_LOG("SetPlaybackCapturerFilterInfos error for g_adProxy null"); + return ERR_OPERATION_FAILED; + } + + std::vector usages; + AUDIO_INFO_LOG("SetPlaybackCapturerFilterInfos"); + for (size_t i = 0; i < options.size(); i++) { + if (count(usages.begin(), usages.end(), options[i].usage) == 0) { + usages.emplace_back(options[i].usage); // deduplicate + } + } + + return gsp->SetSupportStreamUsage(usages); +} + } // namespace AudioStandard } // namespace OHOS diff --git a/services/audio_policy/server/src/service/config/audio_focus_parser.cpp b/services/audio_policy/server/src/service/config/audio_focus_parser.cpp index ab1bfdc0eb70e4da89e30ac4f26321d040e0fa6c..7cf217bb5f78bdc5775fadec34aea030954bd37a 100644 --- a/services/audio_policy/server/src/service/config/audio_focus_parser.cpp +++ b/services/audio_policy/server/src/service/config/audio_focus_parser.cpp @@ -43,7 +43,9 @@ AudioFocusParser::AudioFocusParser() false}}, {"SOURCE_TYPE_VOICE_COMMUNICATION", {AudioStreamType::STREAM_DEFAULT, SourceType::SOURCE_TYPE_VOICE_COMMUNICATION, false}}, - {"SOURCE_TYPE_ULTRASONIC", {AudioStreamType::STREAM_DEFAULT, SourceType::SOURCE_TYPE_ULTRASONIC, false}} + {"SOURCE_TYPE_ULTRASONIC", {AudioStreamType::STREAM_DEFAULT, SourceType::SOURCE_TYPE_ULTRASONIC, false}}, + {"SOURCE_TYPE_PLAYBACK_CAPTURE", {AudioStreamType::STREAM_DEFAULT, + SourceType::SOURCE_TYPE_PLAYBACK_CAPTURE, false}} }; // Initialize action map with string vs InterruptActionType diff --git a/services/audio_policy/server/src/service/manager/audio_adapter_manager.cpp b/services/audio_policy/server/src/service/manager/audio_adapter_manager.cpp index b4f238138876c521e1823eb790d88fbe4c883eed..c1e3df6d501ab69aac4393dbb300e8a337f808b7 100644 --- a/services/audio_policy/server/src/service/manager/audio_adapter_manager.cpp +++ b/services/audio_policy/server/src/service/manager/audio_adapter_manager.cpp @@ -479,6 +479,15 @@ AudioIOHandle AudioAdapterManager::OpenAudioPort(const AudioModuleInfo &audioMod return audioServiceAdapter_->OpenAudioPort(audioModuleInfo.lib, moduleArgs.c_str()); } +AudioIOHandle AudioAdapterManager::LoadLoopback(const LoopbackModuleInfo &moduleInfo) +{ + std::string moduleArgs = GetLoopbackModuleArgs(moduleInfo); + AUDIO_INFO_LOG("[Adapter load-module] %{public}s %{public}s", moduleInfo.lib.c_str(), moduleArgs.c_str()); + + CHECK_AND_RETURN_RET_LOG(audioServiceAdapter_ != nullptr, ERR_OPERATION_FAILED, "ServiceAdapter is null"); + return audioServiceAdapter_->OpenAudioPort(moduleInfo.lib, moduleArgs.c_str()); +} + int32_t AudioAdapterManager::CloseAudioPort(AudioIOHandle ioHandle) { CHECK_AND_RETURN_RET_LOG(audioServiceAdapter_ != nullptr, ERR_OPERATION_FAILED, "ServiceAdapter is null"); @@ -524,6 +533,21 @@ void UpdateCommonArgs(const AudioModuleInfo &audioModuleInfo, std::string &args) } } +std::string AudioAdapterManager::GetLoopbackModuleArgs(const LoopbackModuleInfo &moduleInfo) const +{ + std::string args; + + if (moduleInfo.sink.empty() || moduleInfo.source.empty()) { + return ""; + } + + args.append(" source="); + args.append(moduleInfo.source); + args.append(" sink="); + args.append(moduleInfo.sink); + return args; +} + // Private Members std::string AudioAdapterManager::GetModuleArgs(const AudioModuleInfo &audioModuleInfo) const { @@ -624,6 +648,15 @@ std::string AudioAdapterManager::GetModuleArgs(const AudioModuleInfo &audioModul args.append(" sink_name="); args.append(audioModuleInfo.name); } + if (!audioModuleInfo.sceneName.empty()) { + args.append(" scene_name="); + args.append(audioModuleInfo.sceneName); + } + } else if (audioModuleInfo.lib == INNER_CAPTURER_SINK || audioModuleInfo.lib == RECEIVER_SINK) { + if (!audioModuleInfo.name.empty()) { + args.append(" sink_name="); + args.append(audioModuleInfo.name); + } } return args; } diff --git a/services/audio_service/BUILD.gn b/services/audio_service/BUILD.gn index 5036c7e0d68751b67b6931f530d3e37fe1cd6152..f91df910c3f055fa78dab1a7ac04281a013063e8 100644 --- a/services/audio_service/BUILD.gn +++ b/services/audio_service/BUILD.gn @@ -184,6 +184,7 @@ config("audio_service_config") { "../../frameworks/native/audiopolicy/include", "../../frameworks/native/audioschedule/include", "../../frameworks/native/audioutils/include", + "../../frameworks/native/playbackcapturer/include", "../../frameworks/native/hdiadapter/sink/bluetooth", "../../frameworks/native/hdiadapter/sink/common", "../../frameworks/native/hdiadapter/sink/file", @@ -282,6 +283,7 @@ ohos_shared_library("audio_service") { "../../frameworks/native/hdiadapter/sink:renderer_sink_adapter", "../../frameworks/native/hdiadapter/source:audio_capturer_source", "../../frameworks/native/hdiadapter/source:capturer_source_adapter", + "../../frameworks/native/playbackcapturer:playback_capturer", ] external_deps = [ diff --git a/services/audio_service/client/include/audio_manager_base.h b/services/audio_service/client/include/audio_manager_base.h index 812b9599cc5b36ade58e65a642349a76ff753290..3420ba5e812398de142604a4624bd69080529376 100644 --- a/services/audio_service/client/include/audio_manager_base.h +++ b/services/audio_service/client/include/audio_manager_base.h @@ -207,6 +207,22 @@ public: */ virtual void RequestThreadPriority(uint32_t tid, std::string bundleName) = 0; + /** + * Create playback capturer manager. + * + * @return true/false. + */ + virtual bool CreatePlaybackCapturerManager() = 0; + + /** + * Set StreamUsage set which support playback capturer. + * + * @param usage value of StreamUsage which support inner capturer. + * + * @return result of setting. 0 if success, error number else; + */ + virtual int32_t SetSupportStreamUsage(std::vector usage) = 0; + public: DECLARE_INTERFACE_DESCRIPTOR(u"IStandardAudioService"); }; diff --git a/services/audio_service/client/include/audio_manager_proxy.h b/services/audio_service/client/include/audio_manager_proxy.h index c77613e5bccc8613f8825bc20e8c6261c0e6c876..4ed1d6b5c46d35fdaa6cfa58711057e992a6d2a5 100644 --- a/services/audio_service/client/include/audio_manager_proxy.h +++ b/services/audio_service/client/include/audio_manager_proxy.h @@ -53,6 +53,8 @@ public: bool CreateEffectChainManager(std::vector &effectChains, std::unordered_map &map) override; bool SetOutputDeviceSink(int32_t deviceType, std::string &sinkName) override; + bool CreatePlaybackCapturerManager() override; + int32_t SetSupportStreamUsage(std::vector usage) override; private: static inline BrokerDelegator delegator_; }; diff --git a/services/audio_service/client/include/audio_service_client.h b/services/audio_service/client/include/audio_service_client.h index efbaeb278b3ccb51f4c515ef26797deb3c611eb9..0094a33ddc8ea2e91fa4431f38d01546be97675b 100644 --- a/services/audio_service/client/include/audio_service_client.h +++ b/services/audio_service/client/include/audio_service_client.h @@ -498,6 +498,12 @@ public: * defined in {@link audio_errors.h} otherwise. */ int32_t SetStreamAudioEffectMode(AudioEffectMode effectMode); + + void SetStreamInnerCapturerState(bool isInnerCapturer); + + void SetStreamPrivacyType(AudioPrivacyType privacyType); + + void SetStreamUsage(StreamUsage usage); int32_t SetAudioCaptureMode(AudioCaptureMode captureMode); AudioCaptureMode GetAudioCaptureMode(); @@ -572,6 +578,9 @@ private: bool isMainLoopStarted; bool isContextConnected; bool isStreamConnected; + bool isInnerCapturerStream; + AudioPrivacyType mPrivacyType; + StreamUsage mStreamUsage; std::unique_ptr preBuf_ {nullptr}; uint32_t sinkLatencyInMsec_ {0}; @@ -642,6 +651,7 @@ private: uint32_t underFlowCount; int32_t ConnectStreamToPA(); + const char* GetDeviceNameForConnect(); // Audio cache related functions. These APIs are applicable only for playback scenarios int32_t InitializeAudioCache(); diff --git a/services/audio_service/client/include/pulseaudio_ipc_interface_code.h b/services/audio_service/client/include/pulseaudio_ipc_interface_code.h index 1101626d073eec04e38d2c08e26eb404cdcaa280..0f585b502c92cc1d5efb526877e864f7cdec4c16 100644 --- a/services/audio_service/client/include/pulseaudio_ipc_interface_code.h +++ b/services/audio_service/client/include/pulseaudio_ipc_interface_code.h @@ -46,6 +46,8 @@ namespace AudioStandard { REQUEST_THREAD_PRIORITY = 21, CREATE_AUDIO_EFFECT_CHAIN_MANAGER = 22, SET_OUTPUT_DEVICE_SINK = 23, + CREATE_PLAYBACK_CAPTURER_MANAGER = 24, + SET_SUPPORT_STREAM_USAGE = 25, }; } // namespace AudioStandard } // namespace OHOS diff --git a/services/audio_service/client/src/audio_manager_proxy.cpp b/services/audio_service/client/src/audio_manager_proxy.cpp index 51ac74118464f8b35e3ddc770c3e4317ab3a3d62..c4c03fde89d70b0128bb5a76caf0fff01f39820d 100644 --- a/services/audio_service/client/src/audio_manager_proxy.cpp +++ b/services/audio_service/client/src/audio_manager_proxy.cpp @@ -584,5 +584,54 @@ bool AudioManagerProxy::SetOutputDeviceSink(int32_t deviceType, std::string &sin return true; } +bool AudioManagerProxy::CreatePlaybackCapturerManager() +{ + int32_t error; + MessageParcel data; + MessageParcel reply; + MessageOption option; + if (!data.WriteInterfaceToken(GetDescriptor())) { + AUDIO_ERR_LOG("CreatePlaybackCapturerManager: WriteInterfaceToken failed"); + return false; + } + + error = Remote()->SendRequest( + static_cast(AudioServerInterfaceCode::CREATE_PLAYBACK_CAPTURER_MANAGER), data, reply, option); + if (error != ERR_NONE) { + AUDIO_ERR_LOG("CreatePlaybackCapturerManager failed, error: %{public}d", error); + return false; + } + + return reply.ReadBool(); +} + +int32_t AudioManagerProxy::SetSupportStreamUsage(std::vector usage) +{ + int32_t error; + MessageParcel data; + MessageParcel reply; + MessageOption option; + + if (!data.WriteInterfaceToken(GetDescriptor())) { + AUDIO_ERR_LOG("SetSupportStreamUsage: WriteInterfaceToken failed"); + return -1; + } + + int32_t cnt = (int32_t)usage.size(); + data.WriteInt32(cnt); + for (int32_t i = 0; i < cnt; i++) { + data.WriteInt32(usage[i]); + } + + error = Remote()->SendRequest( + static_cast(AudioServerInterfaceCode::SET_SUPPORT_STREAM_USAGE), data, reply, option); + if (error != ERR_NONE) { + AUDIO_ERR_LOG("SetSupportStreamUsage failed, error: %{public}d", error); + return error; + } + + return reply.ReadInt32(); +} + } // namespace AudioStandard } // namespace OHOS diff --git a/services/audio_service/client/src/audio_service_client.cpp b/services/audio_service/client/src/audio_service_client.cpp index eba257976aa906c976135a6312b81a7b2d47abdf..153e3a4fd43df05e730519d100eb04f97d399efd 100644 --- a/services/audio_service/client/src/audio_service_client.cpp +++ b/services/audio_service/client/src/audio_service_client.cpp @@ -45,6 +45,7 @@ const int32_t NO_OF_PREBUF_TIMES = 6; const string PATH_SEPARATOR = "/"; const string COOKIE_FILE_NAME = "cookie"; +static const string INNER_CAPTURER_SOURCE = "InnerCapturer.monitor"; static int32_t CheckReturnIfinvalid(bool expr, const int32_t retVal) { @@ -521,6 +522,9 @@ AudioServiceClient::AudioServiceClient() setBufferSize = 0; PAStreamCorkSuccessCb = PAStreamStopSuccessCb; rendererSampleRate = 0; + + mPrivacyType = PRIVACY_TYPE_PUBLIC; + mStreamUsage = STREAM_USAGE_UNKNOWN; } void AudioServiceClient::ResetPAAudioClient() @@ -810,6 +814,19 @@ const std::string AudioServiceClient::GetStreamName(AudioStreamType audioType) return streamName; } +const char* AudioServiceClient::GetDeviceNameForConnect() +{ + const char* deviceName = nullptr; + if (eAudioClientType == AUDIO_SERVICE_CLIENT_PLAYBACK) { + std::string selectDevice = AudioSystemManager::GetInstance()->GetSelectedDeviceInfo(clientUid_, clientPid_, + mStreamType); + deviceName = (selectDevice.empty() ? nullptr : selectDevice.c_str()); + } else if (eAudioClientType == AUDIO_SERVICE_CLIENT_RECORD) { + deviceName = isInnerCapturerStream ? INNER_CAPTURER_SOURCE.c_str() : nullptr; + } + return deviceName; +} + int32_t AudioServiceClient::ConnectStreamToPA() { AUDIO_INFO_LOG("Enter AudioServiceClient::ConnectStreamToPA"); @@ -820,9 +837,7 @@ int32_t AudioServiceClient::ConnectStreamToPA() } uint64_t latency_in_msec = AudioSystemManager::GetInstance()->GetAudioLatencyFromXml(); sinkLatencyInMsec_ = AudioSystemManager::GetInstance()->GetSinkLatencyFromXml(); - std::string selectDevice = AudioSystemManager::GetInstance()->GetSelectedDeviceInfo(clientUid_, clientPid_, - mStreamType); - const char *deviceName = (selectDevice.empty() ? nullptr : selectDevice.c_str()); + const char *deviceName = GetDeviceNameForConnect(); pa_threaded_mainloop_lock(mainLoop); @@ -856,7 +871,8 @@ int32_t AudioServiceClient::ConnectStreamToPA() return AUDIO_CLIENT_INIT_ERR; } } else { - result = pa_stream_connect_record(paStream, nullptr, nullptr, + AUDIO_INFO_LOG("pa_stream_connect_record connect to:%{public}s", deviceName ? deviceName : "null"); + result = pa_stream_connect_record(paStream, deviceName, nullptr, (pa_stream_flags_t)(PA_STREAM_INTERPOLATE_TIMING | PA_STREAM_ADJUST_LATENCY | PA_STREAM_START_CORKED @@ -958,6 +974,13 @@ int32_t AudioServiceClient::CreateStream(AudioStreamParams audioParams, AudioStr pa_proplist_sets(propList, "stream.sessionID", std::to_string(pa_context_get_index(context)).c_str()); pa_proplist_sets(propList, "stream.startTime", streamStartTime.c_str()); + if (eAudioClientType == AUDIO_SERVICE_CLIENT_RECORD) { + pa_proplist_sets(propList, "stream.isInnerCapturer", std::to_string(isInnerCapturerStream).c_str()); + } else if (eAudioClientType == AUDIO_SERVICE_CLIENT_PLAYBACK) { + pa_proplist_sets(propList, "stream.privacyType", std::to_string(mPrivacyType).c_str()); + pa_proplist_sets(propList, "stream.usage", std::to_string(mStreamUsage).c_str()); + } + AUDIO_INFO_LOG("Creating stream of channels %{public}d", audioParams.channels); pa_channel_map map; if (audioParams.channels > CHANNEL_6) { @@ -2907,5 +2930,24 @@ int32_t AudioServiceClient::SetStreamAudioEffectMode(AudioEffectMode audioEffect return AUDIO_CLIENT_SUCCESS; } + +void AudioServiceClient::SetStreamInnerCapturerState(bool isInnerCapturer) +{ + AUDIO_DEBUG_LOG("SetStreamInnerCapturerState: %{public}d", isInnerCapturer); + isInnerCapturerStream = isInnerCapturer; +} + +void AudioServiceClient::SetStreamPrivacyType(AudioPrivacyType privacyType) +{ + AUDIO_DEBUG_LOG("SetStreamPrivacyType: %{public}d", privacyType); + mPrivacyType = privacyType; +} + +void AudioServiceClient::SetStreamUsage(StreamUsage usage) +{ + AUDIO_DEBUG_LOG("SetStreamUsage: %{public}d", usage); + mStreamUsage = usage; +} + } // namespace AudioStandard } // namespace OHOS diff --git a/services/audio_service/client/src/audio_stream.cpp b/services/audio_service/client/src/audio_stream.cpp index b2c15314cceeb74634d11710c457b122d691cf60..77d7fed413d1872099b5eecfef969f45f1c11f95 100644 --- a/services/audio_service/client/src/audio_stream.cpp +++ b/services/audio_service/client/src/audio_stream.cpp @@ -135,6 +135,7 @@ AudioStream::~AudioStream() void AudioStream::SetRendererInfo(const AudioRendererInfo &rendererInfo) { rendererInfo_ = rendererInfo; + SetStreamUsage(rendererInfo.streamUsage); } void AudioStream::SetCapturerInfo(const AudioCapturerInfo &capturerInfo) @@ -985,5 +986,15 @@ AudioEffectMode AudioStream::GetAudioEffectMode() { return GetStreamAudioEffectMode(); } + +void AudioStream::SetInnerCapturerState(bool isInnerCapturer) +{ + SetStreamInnerCapturerState(isInnerCapturer); +} + +void AudioStream::SetPrivacyType(AudioPrivacyType privacyType) +{ + SetStreamPrivacyType(privacyType); +} } // namespace AudioStandard } // namespace OHOS diff --git a/services/audio_service/server/include/audio_server.h b/services/audio_service/server/include/audio_server.h index f28cdcfdf02e29d9b7021f8517efc399b611314f..cbdc6b991b8aec377a295185856e1a200e8934c5 100644 --- a/services/audio_service/server/include/audio_server.h +++ b/services/audio_service/server/include/audio_server.h @@ -46,6 +46,7 @@ public: bool LoadAudioEffectLibraries(std::vector libraries, std::vector effects, std::vector& successEffectList) override; + bool CreatePlaybackCapturerManager() override; bool CreateEffectChainManager(std::vector &effectChains, std::unordered_map &map) override; bool SetOutputDeviceSink(int32_t deviceType, std::string &sinkName) override; @@ -81,6 +82,8 @@ public: void RequestThreadPriority(uint32_t tid, std::string bundleName) override; + int32_t SetSupportStreamUsage(std::vector usage) override; + protected: void OnAddSystemAbility(int32_t systemAbilityId, const std::string& deviceId) override; diff --git a/services/audio_service/server/src/audio_manager_stub.cpp b/services/audio_service/server/src/audio_manager_stub.cpp index 33e32b00c10137c6c867cd42a195d00bd2200ff0..8dfc5c7b44406027a579e0cdb4b2a80ae57d7c81 100644 --- a/services/audio_service/server/src/audio_manager_stub.cpp +++ b/services/audio_service/server/src/audio_manager_stub.cpp @@ -287,6 +287,29 @@ int AudioManagerStub::OnRemoteRequest(uint32_t code, MessageParcel &data, Messag } return AUDIO_OK; } + case static_cast(AudioServerInterfaceCode::CREATE_PLAYBACK_CAPTURER_MANAGER): { + bool ret = CreatePlaybackCapturerManager(); + reply.WriteBool(ret); + return AUDIO_OK; + } + case static_cast(AudioServerInterfaceCode::SET_SUPPORT_STREAM_USAGE): { + vector usage; + int32_t tmp_usage; + int32_t cnt = data.ReadInt32(); + CHECK_AND_RETURN_RET_LOG(cnt >= 0 && cnt <= AUDIO_SUPPORTED_STREAM_USAGES.size(), AUDIO_ERR, + "Set support stream usage failed, please check"); + for (int32_t i = 0; i < cnt; i++) { + tmp_usage = data.ReadInt32(); + if (find(AUDIO_SUPPORTED_STREAM_USAGES.begin(), AUDIO_SUPPORTED_STREAM_USAGES.end(), tmp_usage) == + AUDIO_SUPPORTED_STREAM_USAGES.end()) { + continue; + } + usage.emplace_back(tmp_usage); + } + int32_t ret = SetSupportStreamUsage(usage); + reply.WriteInt32(ret); + return AUDIO_OK; + } default: { AUDIO_ERR_LOG("default case, need check AudioManagerStub"); return IPCObjectStub::OnRemoteRequest(code, data, reply, option); diff --git a/services/audio_service/server/src/audio_server.cpp b/services/audio_service/server/src/audio_server.cpp index fb672c2a505d1607b437937dbed05b3ff73c2e88..7d8d81994dfb078a3fc2618831b5fd0f2baaea33 100644 --- a/services/audio_service/server/src/audio_server.cpp +++ b/services/audio_service/server/src/audio_server.cpp @@ -38,6 +38,7 @@ #include "i_audio_renderer_sink.h" #include "i_standard_audio_server_manager_listener.h" #include "audio_effect_chain_manager.h" +#include "playback_capturer_manager.h" #define PA #ifdef PA @@ -738,5 +739,30 @@ void AudioServer::RequestThreadPriority(uint32_t tid, string bundleName) ScheduleReportData(pid, tid, bundleName.c_str()); } +bool AudioServer::CreatePlaybackCapturerManager() +{ + int32_t audio_policy_server_id = 1041; + if (IPCSkeleton::GetCallingUid() != audio_policy_server_id) { + return false; + } + std::vector usage; + PlaybackCapturerManager *playbackCapturerMgr = PlaybackCapturerManager::GetInstance(); + playbackCapturerMgr->SetSupportStreamUsage(usage); + return true; +} + +int32_t AudioServer::SetSupportStreamUsage(std::vector usage) +{ + AUDIO_INFO_LOG("SetSupportStreamUsage with usage num:%{public}zu", usage.size()); + + int32_t audio_policy_server_id = 1041; + if (IPCSkeleton::GetCallingUid() != audio_policy_server_id) { + return ERR_OPERATION_FAILED; + } + PlaybackCapturerManager *playbackCapturerMgr = PlaybackCapturerManager::GetInstance(); + playbackCapturerMgr->SetSupportStreamUsage(usage); + return SUCCESS; +} + } // namespace AudioStandard } // namespace OHOS