Fix a data race in AudioEncoderMutableImpl and derived classes
Before this change, it could happen that a caller would get a pointer to the encoder_ but not use it before another thread called the Reconstruct method, changing the pointer. This of course resulted in bad access crashes. With this change, each use of the pointer acquired from the encoder() method is protected by the same lock that is required to update the pointer. Note that this fix is probably too aggressive, since it also affects the Opus implementation; the crash has so far only been seen for iSAC. Also adding a test to trigger the problem. The test did not trigger the problem deterministically, but out would typically find it in less than 1000 runs. BUG=chromium:499468 R=jmarusic@webrtc.org, kwiberg@webrtc.org Review URL: https://codereview.webrtc.org/1176303004. Cr-Commit-Position: refs/heads/master@{#9436}
This commit is contained in:
@ -17,6 +17,7 @@
|
||||
#include "webrtc/base/thread_annotations.h"
|
||||
#include "webrtc/modules/audio_coding/codecs/audio_encoder.h"
|
||||
#include "webrtc/modules/audio_coding/codecs/g711/include/audio_encoder_pcm.h"
|
||||
#include "webrtc/modules/audio_coding/codecs/isac/main/interface/audio_encoder_isac.h"
|
||||
#include "webrtc/modules/audio_coding/codecs/mock/mock_audio_encoder.h"
|
||||
#include "webrtc/modules/audio_coding/main/acm2/acm_receive_test_oldapi.h"
|
||||
#include "webrtc/modules/audio_coding/main/acm2/acm_send_test_oldapi.h"
|
||||
@ -699,6 +700,160 @@ TEST_F(AcmIsacMtTestOldApi, DISABLED_ON_IOS(DoTest)) {
|
||||
EXPECT_EQ(kEventSignaled, RunTest());
|
||||
}
|
||||
|
||||
class AcmReRegisterIsacMtTestOldApi : public AudioCodingModuleTestOldApi {
|
||||
protected:
|
||||
static const int kRegisterAfterNumPackets = 5;
|
||||
static const int kNumPackets = 10;
|
||||
static const int kPacketSizeMs = 30;
|
||||
static const int kPacketSizeSamples = kPacketSizeMs * 16;
|
||||
|
||||
AcmReRegisterIsacMtTestOldApi()
|
||||
: AudioCodingModuleTestOldApi(),
|
||||
receive_thread_(
|
||||
ThreadWrapper::CreateThread(CbReceiveThread, this, "receive")),
|
||||
codec_registration_thread_(
|
||||
ThreadWrapper::CreateThread(CbCodecRegistrationThread,
|
||||
this,
|
||||
"codec_registration")),
|
||||
test_complete_(EventWrapper::Create()),
|
||||
crit_sect_(CriticalSectionWrapper::CreateCriticalSection()),
|
||||
codec_registered_(false),
|
||||
receive_packet_count_(0),
|
||||
next_insert_packet_time_ms_(0),
|
||||
fake_clock_(new SimulatedClock(0)) {
|
||||
AudioEncoderDecoderIsac::Config config;
|
||||
config.payload_type = kPayloadType;
|
||||
isac_encoder_.reset(new AudioEncoderDecoderIsac(config));
|
||||
clock_ = fake_clock_.get();
|
||||
}
|
||||
|
||||
void SetUp() {
|
||||
AudioCodingModuleTestOldApi::SetUp();
|
||||
// Set up input audio source to read from specified file, loop after 5
|
||||
// seconds, and deliver blocks of 10 ms.
|
||||
const std::string input_file_name =
|
||||
webrtc::test::ResourcePath("audio_coding/speech_mono_16kHz", "pcm");
|
||||
audio_loop_.Init(input_file_name, 5 * kSampleRateHz, kNumSamples10ms);
|
||||
RegisterCodec(); // Must be called before the threads start below.
|
||||
StartThreads();
|
||||
}
|
||||
|
||||
void RegisterCodec() override {
|
||||
static_assert(kSampleRateHz == 16000, "test designed for iSAC 16 kHz");
|
||||
AudioCodingModule::Codec("ISAC", &codec_, kSampleRateHz, 1);
|
||||
codec_.pltype = kPayloadType;
|
||||
|
||||
// Register iSAC codec in ACM, effectively unregistering the PCM16B codec
|
||||
// registered in AudioCodingModuleTestOldApi::SetUp();
|
||||
// Only register the decoder for now. The encoder is registered later.
|
||||
ASSERT_EQ(0, acm_->RegisterReceiveCodec(codec_));
|
||||
}
|
||||
|
||||
void StartThreads() {
|
||||
ASSERT_TRUE(receive_thread_->Start());
|
||||
receive_thread_->SetPriority(kRealtimePriority);
|
||||
ASSERT_TRUE(codec_registration_thread_->Start());
|
||||
codec_registration_thread_->SetPriority(kRealtimePriority);
|
||||
}
|
||||
|
||||
void TearDown() {
|
||||
AudioCodingModuleTestOldApi::TearDown();
|
||||
receive_thread_->Stop();
|
||||
codec_registration_thread_->Stop();
|
||||
}
|
||||
|
||||
EventTypeWrapper RunTest() {
|
||||
return test_complete_->Wait(10 * 60 * 1000); // 10 minutes' timeout.
|
||||
}
|
||||
|
||||
static bool CbReceiveThread(void* context) {
|
||||
return reinterpret_cast<AcmReRegisterIsacMtTestOldApi*>(context)
|
||||
->CbReceiveImpl();
|
||||
}
|
||||
|
||||
bool CbReceiveImpl() {
|
||||
SleepMs(1);
|
||||
const size_t max_encoded_bytes = isac_encoder_->MaxEncodedBytes();
|
||||
rtc::scoped_ptr<uint8_t[]> encoded(new uint8_t[max_encoded_bytes]);
|
||||
AudioEncoder::EncodedInfo info;
|
||||
{
|
||||
CriticalSectionScoped lock(crit_sect_.get());
|
||||
if (clock_->TimeInMilliseconds() < next_insert_packet_time_ms_) {
|
||||
return true;
|
||||
}
|
||||
next_insert_packet_time_ms_ += kPacketSizeMs;
|
||||
++receive_packet_count_;
|
||||
|
||||
// Encode new frame.
|
||||
uint32_t input_timestamp = rtp_header_.header.timestamp;
|
||||
while (info.encoded_bytes == 0) {
|
||||
info = isac_encoder_->Encode(
|
||||
input_timestamp, audio_loop_.GetNextBlock(), kNumSamples10ms,
|
||||
max_encoded_bytes, encoded.get());
|
||||
input_timestamp += 160; // 10 ms at 16 kHz.
|
||||
}
|
||||
EXPECT_EQ(rtp_header_.header.timestamp + kPacketSizeSamples,
|
||||
input_timestamp);
|
||||
EXPECT_EQ(rtp_header_.header.timestamp, info.encoded_timestamp);
|
||||
EXPECT_EQ(rtp_header_.header.payloadType, info.payload_type);
|
||||
}
|
||||
// Now we're not holding the crit sect when calling ACM.
|
||||
|
||||
// Insert into ACM.
|
||||
EXPECT_EQ(0, acm_->IncomingPacket(encoded.get(), info.encoded_bytes,
|
||||
rtp_header_));
|
||||
|
||||
// Pull audio.
|
||||
for (int i = 0; i < rtc::CheckedDivExact(kPacketSizeMs, 10); ++i) {
|
||||
AudioFrame audio_frame;
|
||||
EXPECT_EQ(0, acm_->PlayoutData10Ms(-1 /* default output frequency */,
|
||||
&audio_frame));
|
||||
fake_clock_->AdvanceTimeMilliseconds(10);
|
||||
}
|
||||
rtp_utility_->Forward(&rtp_header_);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool CbCodecRegistrationThread(void* context) {
|
||||
return reinterpret_cast<AcmReRegisterIsacMtTestOldApi*>(context)
|
||||
->CbCodecRegistrationImpl();
|
||||
}
|
||||
|
||||
bool CbCodecRegistrationImpl() {
|
||||
SleepMs(1);
|
||||
if (HasFatalFailure()) {
|
||||
// End the test early if a fatal failure (ASSERT_*) has occurred.
|
||||
test_complete_->Set();
|
||||
}
|
||||
CriticalSectionScoped lock(crit_sect_.get());
|
||||
if (!codec_registered_ &&
|
||||
receive_packet_count_ > kRegisterAfterNumPackets) {
|
||||
// Register the iSAC encoder.
|
||||
EXPECT_EQ(0, acm_->RegisterSendCodec(codec_));
|
||||
codec_registered_ = true;
|
||||
}
|
||||
if (codec_registered_ && receive_packet_count_ > kNumPackets) {
|
||||
test_complete_->Set();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
rtc::scoped_ptr<ThreadWrapper> receive_thread_;
|
||||
rtc::scoped_ptr<ThreadWrapper> codec_registration_thread_;
|
||||
const rtc::scoped_ptr<EventWrapper> test_complete_;
|
||||
const rtc::scoped_ptr<CriticalSectionWrapper> crit_sect_;
|
||||
bool codec_registered_ GUARDED_BY(crit_sect_);
|
||||
int receive_packet_count_ GUARDED_BY(crit_sect_);
|
||||
int64_t next_insert_packet_time_ms_ GUARDED_BY(crit_sect_);
|
||||
rtc::scoped_ptr<AudioEncoderDecoderIsac> isac_encoder_;
|
||||
rtc::scoped_ptr<SimulatedClock> fake_clock_;
|
||||
test::AudioLoop audio_loop_;
|
||||
};
|
||||
|
||||
TEST_F(AcmReRegisterIsacMtTestOldApi, DISABLED_ON_IOS(DoTest)) {
|
||||
EXPECT_EQ(kEventSignaled, RunTest());
|
||||
}
|
||||
|
||||
// Disabling all of these tests on iOS until file support has been added.
|
||||
// See https://code.google.com/p/webrtc/issues/detail?id=4752 for details.
|
||||
#if !defined(WEBRTC_IOS)
|
||||
|
||||
Reference in New Issue
Block a user