Sound is gone when unplugging the input device during WebRTC
Categories
(Core :: Audio/Video: cubeb, defect, P2)
Tracking
()
People
(Reporter: chunmin, Assigned: chunmin)
References
Details
Attachments
(2 files)
This only happen when the default input device is set to some certain device. In my case, the problem can be reproduced with e-Kit MIC-U01 but cannot be reproduced with Plantronic .Audio 628.
Step to reproduce
- Set the system default input device to an input-only USB device D (e.g., e-Kit MIC-U01)
- Make a WebRTC call on computer A
- Use device B to join the WebRTC call
- Unplug the input device D
Expect
I can still hear the sound from device B via the my output device
Actual
No sound can be heard anymore
Assignee | ||
Comment 1•5 years ago
|
||
It seems it only happens in C backend. The Rust backend works fine. I haven't found the root cause so I don't know why Rust backend can work.
It might be a deadlock issue. The camera can still work. However, the sound cannot be heard from the output device anymore. Not sure if it's possible to keep camera alive when the audio thread is locked.
The output callback thread always stops at here and reinit thread always stops at here then here. It looks like the mutex inside the input AudioUnit is hold by something, but that's just my guess.
Comment 2•5 years ago
|
||
(In reply to C.M.Chang[:chunmin] from comment #1)
It might be a deadlock issue. The camera can still work. However, the sound cannot be heard from the output device anymore. Not sure if it's possible to keep camera alive when the audio thread is locked.
It is nowadays. We still need the audio thread to connect media elements to the video track (adding a listener) and the like, but once set up, frames can flow without it.
Comment 3•5 years ago
|
||
Can you please try removing the AudioOutputUnitStop() call in [1] to verify that the problem lies in there? The reinit will stop the stream anyway. It is strange that we get a reinit and a drain at the same time. It would be interesting to know which one comes first.
Assignee | ||
Comment 4•5 years ago
|
||
(In reply to Alex Chronopoulos [:achronop] from comment #3)
Can you please try removing the AudioOutputUnitStop() call in [1] to verify that the problem lies in there?
The problem is gone after removing the AudioOutputUnitStop(stm->input_unit)
in that line.
It would be interesting to know which one comes first.
In my case, reinit always comes first and stop when calling AudioOutputUnitStop(stm->input_unit)
in audiounit_stream_stop_internal
then output callback comes later.
Assignee | ||
Comment 5•5 years ago
|
||
The problem is gone if I don't use the aggregate device by commenting these lines. The reason I tried not using the aggregate device is because I guess we have a deadlock between two mutexes inside the CoreAudio library. I'll explain the deadlock I observed in the later comment.
Assignee | ||
Comment 6•5 years ago
•
|
||
I used lldb to monitor the threads when the problem happened. There are four threads waiting for the mutex.
thread #46: tid = 0x5c844, 0x00007fff729aff06 libsystem_kernel.dylib`__psynch_mutexwait + 10, queue = 'org.mozilla.cubeb'
thread #47: tid = 0x5c893, 0x00007fff729aff06 libsystem_kernel.dylib`__psynch_mutexwait + 10, queue = 'com.apple.audio.CADispatchQueue.SerialQueue'
thread #77: tid = 0x5c990, 0x00007fff729aff06 libsystem_kernel.dylib`__psynch_mutexwait + 10, name = 'com.apple.audio.IOThread.client'
thread #78: tid = 0x5c9a4, 0x00007fff729aff06 libsystem_kernel.dylib`__psynch_mutexwait + 10, name = 'CubebOp~tion #2'
- thread #46 is a reinit thread, which is waiting for a
CAMutex
- thread #47 is an internal thread fired for hardware property changed, which is waiting for a
HALB_Mutex
- thread #77 is a Output callback thread, which is waiting for a
CAMutex
- thread #78 is a task thread of AsyncCubebOperation from GraphDriver, which is waiting for a
owned_critical_section
Here is my hypothesis after checking their stacks.
- thread #46 holds a mutex for cubeb context, which is the mutex thread #78 is waiting for.
- thread #46 is waiting for a
CAMutex
, which is tied to inputAudioUnit
. - From my past experience, there should a mutex for an AudioUnit. The mutex will be held when the data callback tied to the
AudioUnit
is running. - thread #77 is waiting for a
CAMutex
, which is tied to inputAudioUnit
- By 2, thread #77 also hold a
CAMutex
tied to outputAudioUnit
so that's whyAudioOutputUnitStop(stm->output_unit)
works fine. - The
CAMutex
tied to inputAudioUnit
is held by something since thread #46 and thread #77 are waiting for it. HALB_Mutex
is held by something since thread #47 is waiting for it.- If there is a deadlock exist among these four threads, it's reasonable to believe that thread #47 holds the
CAMutex
tied to inputAudioUnit
and thread #77 holds theHALB_Mutex
In this case, the input device and output device are different, so they are merged into one aggregate device. If HAL means hardware abstraction layer here, the hardware is likely to be referred to the created aggregate device. The created aggregate device is assigned to the device of the input and output AudioUnit
in our code, hence it makes sense to guess the HALB_Mutex
tied to the aggregate device is held when the output callback (thread #77) is running. Another reason to believe is that the problem is gone by not using the aggregate device (comment 5). I guess it is because the HALB_Mutex
tied to the hardware are different for input and output when the input device and output device are separate.
Stacks of these four threads
Thread 46 (Reinit thread)
* thread #46, queue = 'org.mozilla.cubeb'
* frame #0: 0x00007fff729aff06 libsystem_kernel.dylib`__psynch_mutexwait + 10
frame #1: 0x00007fff72a66d52 libsystem_pthread.dylib`_pthread_mutex_firstfit_lock_wait + 96
frame #2: 0x00007fff72a644cd libsystem_pthread.dylib`_pthread_mutex_firstfit_lock_slow + 222
frame #3: 0x000000018de0ae75 CoreAudio`CAMutex::Lock() + 67
frame #4: 0x000000018df2fef3 CoreAudio`AUMethodStop(void*) + 39
frame #5: 0x0000000119ff7899 XUL`::___ZL29audiounit_reinit_stream_asyncP12cubeb_streamj_block_invoke_2() [inlined] audiounit_stream_stop_internal(stm=<unavailable>) at cubeb_audiounit.cpp:2945:9 [opt]
frame #6: 0x0000000119ff7888 XUL`::___ZL29audiounit_reinit_stream_asyncP12cubeb_streamj_block_invoke_2() [inlined] audiounit_reinit_stream(stm=0x0000000108354c00, flags=3) at cubeb_audiounit.cpp:806 [opt]
frame #7: 0x0000000119ff783e XUL`::___ZL29audiounit_reinit_stream_asyncP12cubeb_streamj_block_invoke_2(.block_descriptor=0x0000000165dcf220) at cubeb_audiounit.cpp:894 [opt]
frame #8: 0x00007fff7282a5f8 libdispatch.dylib`_dispatch_call_block_and_release + 12
frame #9: 0x00007fff7282b63d libdispatch.dylib`_dispatch_client_callout + 8
frame #10: 0x00007fff728318e0 libdispatch.dylib`_dispatch_lane_serial_drain + 602
frame #11: 0x00007fff72832396 libdispatch.dylib`_dispatch_lane_invoke + 385
frame #12: 0x00007fff7283a6ed libdispatch.dylib`_dispatch_workloop_worker_thread + 598
frame #13: 0x00007fff72a65611 libsystem_pthread.dylib`_pthread_wqthread + 421
frame #14: 0x00007fff72a653fd libsystem_pthread.dylib`start_wqthread + 13
Thread 47 (Property Changed from HAL, it’s a internal thread we won’t touch)
* thread #47, queue = 'com.apple.audio.CADispatchQueue.SerialQueue'
* frame #0: 0x00007fff729aff06 libsystem_kernel.dylib`__psynch_mutexwait + 10
frame #1: 0x00007fff72a66d52 libsystem_pthread.dylib`_pthread_mutex_firstfit_lock_wait + 96
frame #2: 0x00007fff72a644cd libsystem_pthread.dylib`_pthread_mutex_firstfit_lock_slow + 222
frame #3: 0x00007fff45e45068 CoreAudio`HALB_Mutex::Lock() + 92
frame #4: 0x00007fff45e7aa3f CoreAudio`HALC_ProxyIOContext::StopIOProc(int (*)(unsigned int, AudioTimeStamp const*, AudioBufferList const*, AudioTimeStamp const*, AudioBufferList*, AudioTimeStamp const*, void*)) + 43
frame #5: 0x00007fff45e7a937 CoreAudio`HALC_ShellDevice::StopIOProc(int (*)(unsigned int, AudioTimeStamp const*, AudioBufferList const*, AudioTimeStamp const*, AudioBufferList*, AudioTimeStamp const*, void*)) + 75
frame #6: 0x00007fff45e7a6c4 CoreAudio`HAL_HardwarePlugIn_DeviceStop(AudioHardwarePlugInInterface**, unsigned int, int (*)(unsigned int, AudioTimeStamp const*, AudioBufferList const*, AudioTimeStamp const*, AudioBufferList*, AudioTimeStamp const*, void*)) + 181
frame #7: 0x00007fff45e7a4f5 CoreAudio`HALPlugIn::DeviceStopIOProc(HALObject const&, int (*)(unsigned int, AudioTimeStamp const*, AudioBufferList const*, AudioTimeStamp const*, AudioBufferList*, AudioTimeStamp const*, void*)) + 41
frame #8: 0x00007fff45e7a4aa CoreAudio`HALDevice::StopIOProc(int (*)(unsigned int, AudioTimeStamp const*, AudioBufferList const*, AudioTimeStamp const*, AudioBufferList*, AudioTimeStamp const*, void*)) + 200
frame #9: 0x00007fff45e7a0dd CoreAudio`AudioDeviceStop + 103
frame #10: 0x000000018de0f120 CoreAudio`AUHAL::StopHardware(bool) + 60
frame #11: 0x000000018de0f2c5 CoreAudio`AUHAL::Stop() + 161
frame #12: 0x000000018de02bdb CoreAudio`AUHAL::SelectDevice(unsigned int) + 553
frame #13: 0x000000018de0dad9 CoreAudio`AUHAL::DeviceListener(unsigned int, unsigned int, AudioObjectPropertyAddress const*, void*) + 569
frame #14: 0x00007fff45e6793e CoreAudio`HALPropertyListener::Call(unsigned int, unsigned int, AudioObjectPropertyAddress const*) + 282
frame #15: 0x00007fff45e5bec8 CoreAudio`HALObject::PropertiesChanged(unsigned int, AudioObjectPropertyAddress const*) + 1280
frame #16: 0x00007fff45e6073f CoreAudio`HALDevice::PropertiesChanged(unsigned int, AudioObjectPropertyAddress const*) + 421
frame #17: 0x00007fff45e7848b CoreAudio`HALSystem::AudioObjectPropertiesChanged(AudioHardwarePlugInInterface**, unsigned int, unsigned int, AudioObjectPropertyAddress const*) + 163
frame #18: 0x00007fff45e788d0 CoreAudio`_SendPropertiesChanged(void*) + 41
frame #19: 0x00007fff7282b63d libdispatch.dylib`_dispatch_client_callout + 8
frame #20: 0x00007fff728318e0 libdispatch.dylib`_dispatch_lane_serial_drain + 602
frame #21: 0x00007fff72832396 libdispatch.dylib`_dispatch_lane_invoke + 385
frame #22: 0x00007fff7283a6ed libdispatch.dylib`_dispatch_workloop_worker_thread + 598
frame #23: 0x00007fff72a65611 libsystem_pthread.dylib`_pthread_wqthread + 421
frame #24: 0x00007fff72a653fd libsystem_pthread.dylib`start_wqthread + 13
Thread 77 (Output callback)
* thread #77, name = 'com.apple.audio.IOThread.client'
* frame #0: 0x00007fff729aff06 libsystem_kernel.dylib`__psynch_mutexwait + 10
frame #1: 0x00007fff72a66d52 libsystem_pthread.dylib`_pthread_mutex_firstfit_lock_wait + 96
frame #2: 0x00007fff72a644cd libsystem_pthread.dylib`_pthread_mutex_firstfit_lock_slow + 222
frame #3: 0x000000018de0ae75 CoreAudio`CAMutex::Lock() + 67
frame #4: 0x000000018df2fef3 CoreAudio`AUMethodStop(void*) + 39
frame #5: 0x0000000119ff8294 XUL`audiounit_output_callback(user_ptr=0x0000000108354c00, (null)=<unavailable>, tstamp=<unavailable>, bus=<unavailable>, output_frames=512, outBufferList=0x000000018ef26720) at cubeb_audiounit.cpp:618:11 [opt]
frame #6: 0x000000018de0cb0f CoreAudio`AUConverterBase::RenderBus(unsigned int&, AudioTimeStamp const&, unsigned int, unsigned int) + 729
frame #7: 0x000000018df28647 CoreAudio`AUBase::DoRenderBus(unsigned int&, AudioTimeStamp const&, unsigned int, AUOutputElement*, unsigned int, AudioBufferList&) + 169
frame #8: 0x000000018df27da7 CoreAudio`AUBase::DoRender(unsigned int&, AudioTimeStamp const&, unsigned int, unsigned int, AudioBufferList&) + 601
frame #9: 0x000000018de0ec96 CoreAudio`AUHAL::AUIOProc(unsigned int, AudioTimeStamp const*, AudioBufferList const*, AudioTimeStamp const*, AudioBufferList*, AudioTimeStamp const*, void*) + 1704
frame #10: 0x00007fff45e76ed3 CoreAudio`HALC_ProxyIOContext::IOWorkLoop() + 3885
frame #11: 0x00007fff45e75df4 CoreAudio`HALC_ProxyIOContext::IOThreadEntry(void*) + 122
frame #12: 0x00007fff45e75956 CoreAudio`HALB_IOThread::Entry(void*) + 72
frame #13: 0x00007fff72a662eb libsystem_pthread.dylib`_pthread_body + 126
frame #14: 0x00007fff72a69249 libsystem_pthread.dylib`_pthread_start + 66
frame #15: 0x00007fff72a6540d libsystem_pthread.dylib`thread_start + 13
Thread 78 (AsyncCubebOperation)
* thread #78, name = 'CubebOp~tion #2'
* frame #0: 0x00007fff729aff06 libsystem_kernel.dylib`__psynch_mutexwait + 10
frame #1: 0x00007fff72a66d52 libsystem_pthread.dylib`_pthread_mutex_firstfit_lock_wait + 96
frame #2: 0x00007fff72a644cd libsystem_pthread.dylib`_pthread_mutex_firstfit_lock_slow + 222
frame #3: 0x0000000119ff1539 XUL`audiounit_stream_stop(cubeb_stream*) [inlined] owned_critical_section::lock(this=<unavailable>) at cubeb_utils_unix.h:56:5 [opt]
frame #4: 0x0000000119ff152d XUL`audiounit_stream_stop(cubeb_stream*) [inlined] std::__1::lock_guard<owned_critical_section>::lock_guard(this=<unavailable>, __m=<unavailable>) at __mutex_base:104 [opt]
frame #5: 0x0000000119ff152d XUL`audiounit_stream_stop(cubeb_stream*) [inlined] std::__1::lock_guard<owned_critical_section>::lock_guard(this=<unavailable>, __m=<unavailable>) at __mutex_base:104 [opt]
frame #6: 0x0000000119ff152d XUL`audiounit_stream_stop(stm=0x0000000108354c00) at cubeb_audiounit.cpp:2957 [opt]
frame #7: 0x00000001187af7bd XUL`mozilla::AudioCallbackDriver::Stop(this=0x000000018a0f1600) at GraphDriver.cpp:740:7 [opt]
frame #8: 0x00000001187aea8b XUL`mozilla::AsyncCubebTask::Run(this=0x000000010bd14c80) at GraphDriver.cpp:462:16 [opt]
frame #9: 0x00000001157c15bc XUL`nsThreadPool::Run(this=<unavailable>) at nsThreadPool.cpp:244:14 [opt]
frame #10: 0x00000001157c197d XUL`non-virtual thunk to nsThreadPool::Run(this=<unavailable>) at nsThreadPool.cpp:0 [opt]
frame #11: 0x00000001157ba9fc XUL`nsThread::ProcessNextEvent(this=0x000000010bc772f0, aMayWait=<unavailable>, aResult=0x000070000a59edef) at nsThread.cpp:1225:14 [opt]
frame #12: 0x00000001157be928 XUL`NS_ProcessNextEvent(aThread=<unavailable>, aMayWait=<unavailable>) at nsThreadUtils.cpp:486:10 [opt]
frame #13: 0x0000000115ef6ccd XUL`mozilla::ipc::MessagePumpForNonMainThreads::Run(this=0x000000010bd46ac0, aDelegate=0x000000018fecef40) at MessagePump.cpp:333:5 [opt]
frame #14: 0x0000000115e88ef5 XUL`MessageLoop::Run() [inlined] MessageLoop::RunHandler(this=<unavailable>) at message_loop.cc:308:3 [opt]
frame #15: 0x0000000115e88ef0 XUL`MessageLoop::Run(this=<unavailable>) at message_loop.cc:290 [opt]
frame #16: 0x00000001157b6f14 XUL`nsThread::ThreadFunc(aArg=<unavailable>) at nsThread.cpp:458:11 [opt]
frame #17: 0x0000000107e24ff0 libnss3.dylib`_pt_root(arg=0x000000018d8262f0) at ptthread.c:198:5 [opt]
frame #18: 0x00007fff72a662eb libsystem_pthread.dylib`_pthread_body + 126
frame #19: 0x00007fff72a69249 libsystem_pthread.dylib`_pthread_start + 66
frame #20: 0x00007fff72a6540d libsystem_pthread.dylib`thread_start + 13
Comment 7•5 years ago
|
||
Can you try using the reinit_pending
flag [1] in audiounit_output_callback
to exit callback without stopping anything? We can do the same thing we do in shutdown [2].
[1] https://searchfox.org/mozilla-central/source/media/libcubeb/src/cubeb_audiounit.cpp#239
[2] https://searchfox.org/mozilla-central/rev/9775cca0a10a9b5c5f4e15c8f7b3eff5bf91bbd0/media/libcubeb/src/cubeb_audiounit.cpp#608-612
Assignee | ||
Comment 8•5 years ago
•
|
||
The reason why Rust backend works is that the stream shutdown is called before output drains hence the output callback doesn’t try to get the CAMutex (it only happens when draining). As a result, the deadlock between output callback and the internal callback of the hardware device change inside the CoreAudio won’t be formed.
The following are the code sequences I observed:
C backend
- reinit: start then pending on trying to get a
CAMutex
to stopAudioUnit
s, holding the mutex for Cubeb context - output callback: device switching, feed silence
- ....
- output callback: device switching, feed silence
- stream stop: start then pending on trying to get the mutex for Cubeb context
- output callback: drains and pending on trying to get a
CAMutex
Rust backend
- reinit: start then pending on trying to get a
CAMutex
to stopAudioUnit
s - output callback: device switching, feed silence
- ....
- output callback: device switching, feed silence
- stream stop: start and ask stream to shut down, pending on trying to get CAMutex to stop the audio units
- output callback: output shutdown, feed silence
- …..
- output callback: output shutdown, feed silence
- reinit: get the
CAMutex
and stopAudioUnit
s, start reinitializing the stream - stream stop: Get the
CAMutex
and stopAudioUnit
s again. - reinit: finish reinit stuff
(BTW, I smell a potential problem: during reinit, we will stop the stream to do some reinit settings. If the stream starts is called in the middle of the stream reinitialization, we might have a racing issue. However, I don't think this exists in gecko.)
Assignee | ||
Comment 9•5 years ago
|
||
(In reply to Alex Chronopoulos [:achronop] from comment #3)
It is strange that we get a reinit and a drain at the same time.
It looks like the draining is intended.
Assignee | ||
Comment 10•5 years ago
|
||
(In reply to Alex Chronopoulos [:achronop] from comment #7)
Can you try using the
reinit_pending
flag [1] inaudiounit_output_callback
to exit callback without stopping anything?
That works. Then we don't need to get the CAMutex
when the device changing is working. I'll submit a pull-request in the cubeb repo later.
We can do the same thing we do in shutdown [2].
I think we can leave the code as it is since we don't try getting another mutex when the stream is shutting down.
Comment 11•5 years ago
|
||
Do you think you can create a patch on cubeb repo? I can review it.
Assignee | ||
Comment 12•5 years ago
•
|
||
(In reply to Alex Chronopoulos [:achronop] from comment #11)
Do you think you can create a patch on cubeb repo? I can review it.
Thanks! here is the pull request: https://github.com/kinetiknz/cubeb/pull/528
try result is here: https://treeherder.mozilla.org/#/jobs?repo=try&revision=8b2368127419bb229a9a42fd07fb2883ca4a580f
Assignee | ||
Comment 13•5 years ago
•
|
||
(In reply to C.M.Chang[:chunmin] from comment #8)
The reason why Rust backend works is that the stream shutdown is called before output drains hence the output callback doesn’t try to get the CAMutex (it only happens when draining). As a result, the deadlock between output callback and the internal callback of the hardware device change inside the CoreAudio won’t be formed.
Technically, in the Rust backend, the deadlock might be formed. The GraphDriver will initiate a stream stop/shutdown task and then set the stream to drain in an output callback when it detects the device switching. Then, if the next output callback comes before the stream stop/shutdown task, the next output callback will try requiring the CAMutex
for the input AudioUnit and then forms a deadlock. However, I cannot produce this on my MacBook.
Here is the code sequence when I do the STR:
- reinit: start then pending on trying to get a
CAMutex
to stopAudioUnit
s - output callback: device switching, feed silence to the input buffer
- ....
- output callback: device switching, feed silence to the input buffer and set stream to drain
- stream stop: start and ask stream to shut down, pending on trying to get CAMutex to stop the audio units
- output callback: output shutdown, feed silence to the output buffer
- …..
- output callback: output shutdown, feed silence to the output buffer
- reinit: get the
CAMutex
and stopAudioUnit
s, start reinitializing the stream - stream stop: Get the
CAMutex
and stopAudioUnit
s again. - reinit: finish reinit stuff
To be specific, if the output callback thread at 6 above comes before the the stream stop thread at 5 above, then the deadlock might be formed.
The cubeb stream shutdown task will be initiated before the stream is set to drain in the output callback, but technically it doesn't guarantee the shutdown task runs before the next output callback.
However, in my MacBook, the stream stop/shutdown always comes before the stream is drained.
Assignee | ||
Comment 14•5 years ago
•
|
||
(In reply to C.M.Chang[:chunmin] from comment #13)
Technically, in the Rust backend, the deadlock might be formed. The GraphDriver will initiat a stream stop/shutdown task and then set the stream to drain in a output callback when it detects the device switching. Then, if the next output callback comes before the stream stop/shutdown task, the next output callback will try requiring the
CAMutex
for the input AudioUnit and then forms a deadlock. However, I cannot produce this on my MacBook.
This can be proven by changing some code. With the STR, the deadlock can be produced by either commenting the setting of shutdown or returning an error directly when stream stop is called. The thread stacks (by lldb) are pretty similar.
Assignee | ||
Comment 15•5 years ago
|
||
Assignee | ||
Comment 16•5 years ago
|
||
cubeb #528 is imported by bug 1603384. I am going to mark this resolved.
Description
•