After https://codereview.webrtc.org/1827263002, audio devices are no longer (ever) initialized if they return true from RecordingIsInitialized. Since this was left as "return true;" for file_audio_device, the recording buffer was never set up correctly, and the audio buffer would assert when called (in debug) and FileAudioDevice would cause memory corruption (in release). BUG= Review-Url: https://codereview.webrtc.org/2116003003 Cr-Commit-Position: refs/heads/master@{#13489}
536 lines
14 KiB
C++
536 lines
14 KiB
C++
/*
|
|
* Copyright (c) 2014 The WebRTC project authors. All Rights Reserved.
|
|
*
|
|
* Use of this source code is governed by a BSD-style license
|
|
* that can be found in the LICENSE file in the root of the source
|
|
* tree. An additional intellectual property rights grant can be found
|
|
* in the file PATENTS. All contributing project authors may
|
|
* be found in the AUTHORS file in the root of the source tree.
|
|
*/
|
|
#include "webrtc/base/platform_thread.h"
|
|
#include "webrtc/modules/audio_device/dummy/file_audio_device.h"
|
|
#include "webrtc/system_wrappers/include/sleep.h"
|
|
|
|
namespace webrtc {
|
|
|
|
const int kRecordingFixedSampleRate = 48000;
|
|
const size_t kRecordingNumChannels = 2;
|
|
const int kPlayoutFixedSampleRate = 48000;
|
|
const size_t kPlayoutNumChannels = 2;
|
|
const size_t kPlayoutBufferSize =
|
|
kPlayoutFixedSampleRate / 100 * kPlayoutNumChannels * 2;
|
|
const size_t kRecordingBufferSize =
|
|
kRecordingFixedSampleRate / 100 * kRecordingNumChannels * 2;
|
|
|
|
FileAudioDevice::FileAudioDevice(const int32_t id,
|
|
const char* inputFilename,
|
|
const char* outputFilename):
|
|
_ptrAudioBuffer(NULL),
|
|
_recordingBuffer(NULL),
|
|
_playoutBuffer(NULL),
|
|
_recordingFramesLeft(0),
|
|
_playoutFramesLeft(0),
|
|
_critSect(*CriticalSectionWrapper::CreateCriticalSection()),
|
|
_recordingBufferSizeIn10MS(0),
|
|
_recordingFramesIn10MS(0),
|
|
_playoutFramesIn10MS(0),
|
|
_playing(false),
|
|
_recording(false),
|
|
_lastCallPlayoutMillis(0),
|
|
_lastCallRecordMillis(0),
|
|
_outputFile(*FileWrapper::Create()),
|
|
_inputFile(*FileWrapper::Create()),
|
|
_outputFilename(outputFilename),
|
|
_inputFilename(inputFilename),
|
|
_clock(Clock::GetRealTimeClock()) {
|
|
}
|
|
|
|
FileAudioDevice::~FileAudioDevice() {
|
|
delete &_outputFile;
|
|
delete &_inputFile;
|
|
}
|
|
|
|
int32_t FileAudioDevice::ActiveAudioLayer(
|
|
AudioDeviceModule::AudioLayer& audioLayer) const {
|
|
return -1;
|
|
}
|
|
|
|
AudioDeviceGeneric::InitStatus FileAudioDevice::Init() {
|
|
return InitStatus::OK;
|
|
}
|
|
|
|
int32_t FileAudioDevice::Terminate() { return 0; }
|
|
|
|
bool FileAudioDevice::Initialized() const { return true; }
|
|
|
|
int16_t FileAudioDevice::PlayoutDevices() {
|
|
return 1;
|
|
}
|
|
|
|
int16_t FileAudioDevice::RecordingDevices() {
|
|
return 1;
|
|
}
|
|
|
|
int32_t FileAudioDevice::PlayoutDeviceName(uint16_t index,
|
|
char name[kAdmMaxDeviceNameSize],
|
|
char guid[kAdmMaxGuidSize]) {
|
|
const char* kName = "dummy_device";
|
|
const char* kGuid = "dummy_device_unique_id";
|
|
if (index < 1) {
|
|
memset(name, 0, kAdmMaxDeviceNameSize);
|
|
memset(guid, 0, kAdmMaxGuidSize);
|
|
memcpy(name, kName, strlen(kName));
|
|
memcpy(guid, kGuid, strlen(guid));
|
|
return 0;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
int32_t FileAudioDevice::RecordingDeviceName(uint16_t index,
|
|
char name[kAdmMaxDeviceNameSize],
|
|
char guid[kAdmMaxGuidSize]) {
|
|
const char* kName = "dummy_device";
|
|
const char* kGuid = "dummy_device_unique_id";
|
|
if (index < 1) {
|
|
memset(name, 0, kAdmMaxDeviceNameSize);
|
|
memset(guid, 0, kAdmMaxGuidSize);
|
|
memcpy(name, kName, strlen(kName));
|
|
memcpy(guid, kGuid, strlen(guid));
|
|
return 0;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
int32_t FileAudioDevice::SetPlayoutDevice(uint16_t index) {
|
|
if (index == 0) {
|
|
_playout_index = index;
|
|
return 0;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
int32_t FileAudioDevice::SetPlayoutDevice(
|
|
AudioDeviceModule::WindowsDeviceType device) {
|
|
return -1;
|
|
}
|
|
|
|
int32_t FileAudioDevice::SetRecordingDevice(uint16_t index) {
|
|
if (index == 0) {
|
|
_record_index = index;
|
|
return _record_index;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
int32_t FileAudioDevice::SetRecordingDevice(
|
|
AudioDeviceModule::WindowsDeviceType device) {
|
|
return -1;
|
|
}
|
|
|
|
int32_t FileAudioDevice::PlayoutIsAvailable(bool& available) {
|
|
if (_playout_index == 0) {
|
|
available = true;
|
|
return _playout_index;
|
|
}
|
|
available = false;
|
|
return -1;
|
|
}
|
|
|
|
int32_t FileAudioDevice::InitPlayout() {
|
|
if (_ptrAudioBuffer) {
|
|
// Update webrtc audio buffer with the selected parameters
|
|
_ptrAudioBuffer->SetPlayoutSampleRate(kPlayoutFixedSampleRate);
|
|
_ptrAudioBuffer->SetPlayoutChannels(kPlayoutNumChannels);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
bool FileAudioDevice::PlayoutIsInitialized() const {
|
|
return true;
|
|
}
|
|
|
|
int32_t FileAudioDevice::RecordingIsAvailable(bool& available) {
|
|
if (_record_index == 0) {
|
|
available = true;
|
|
return _record_index;
|
|
}
|
|
available = false;
|
|
return -1;
|
|
}
|
|
|
|
int32_t FileAudioDevice::InitRecording() {
|
|
CriticalSectionScoped lock(&_critSect);
|
|
|
|
if (_recording) {
|
|
return -1;
|
|
}
|
|
|
|
_recordingFramesIn10MS = static_cast<size_t>(kRecordingFixedSampleRate / 100);
|
|
|
|
if (_ptrAudioBuffer) {
|
|
_ptrAudioBuffer->SetRecordingSampleRate(kRecordingFixedSampleRate);
|
|
_ptrAudioBuffer->SetRecordingChannels(kRecordingNumChannels);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
bool FileAudioDevice::RecordingIsInitialized() const {
|
|
return _recordingFramesIn10MS != 0;
|
|
}
|
|
|
|
int32_t FileAudioDevice::StartPlayout() {
|
|
if (_playing) {
|
|
return 0;
|
|
}
|
|
|
|
_playoutFramesIn10MS = static_cast<size_t>(kPlayoutFixedSampleRate / 100);
|
|
_playing = true;
|
|
_playoutFramesLeft = 0;
|
|
|
|
if (!_playoutBuffer) {
|
|
_playoutBuffer = new int8_t[kPlayoutBufferSize];
|
|
}
|
|
if (!_playoutBuffer) {
|
|
_playing = false;
|
|
return -1;
|
|
}
|
|
|
|
// PLAYOUT
|
|
if (!_outputFilename.empty() &&
|
|
!_outputFile.OpenFile(_outputFilename.c_str(), false)) {
|
|
printf("Failed to open playout file %s!\n", _outputFilename.c_str());
|
|
_playing = false;
|
|
delete [] _playoutBuffer;
|
|
_playoutBuffer = NULL;
|
|
return -1;
|
|
}
|
|
|
|
_ptrThreadPlay.reset(new rtc::PlatformThread(
|
|
PlayThreadFunc, this, "webrtc_audio_module_play_thread"));
|
|
_ptrThreadPlay->Start();
|
|
_ptrThreadPlay->SetPriority(rtc::kRealtimePriority);
|
|
return 0;
|
|
}
|
|
|
|
int32_t FileAudioDevice::StopPlayout() {
|
|
{
|
|
CriticalSectionScoped lock(&_critSect);
|
|
_playing = false;
|
|
}
|
|
|
|
// stop playout thread first
|
|
if (_ptrThreadPlay) {
|
|
_ptrThreadPlay->Stop();
|
|
_ptrThreadPlay.reset();
|
|
}
|
|
|
|
CriticalSectionScoped lock(&_critSect);
|
|
|
|
_playoutFramesLeft = 0;
|
|
delete [] _playoutBuffer;
|
|
_playoutBuffer = NULL;
|
|
_outputFile.CloseFile();
|
|
return 0;
|
|
}
|
|
|
|
bool FileAudioDevice::Playing() const {
|
|
return true;
|
|
}
|
|
|
|
int32_t FileAudioDevice::StartRecording() {
|
|
_recording = true;
|
|
|
|
// Make sure we only create the buffer once.
|
|
_recordingBufferSizeIn10MS = _recordingFramesIn10MS *
|
|
kRecordingNumChannels *
|
|
2;
|
|
if (!_recordingBuffer) {
|
|
_recordingBuffer = new int8_t[_recordingBufferSizeIn10MS];
|
|
}
|
|
|
|
if (!_inputFilename.empty() &&
|
|
!_inputFile.OpenFile(_inputFilename.c_str(), true)) {
|
|
printf("Failed to open audio input file %s!\n",
|
|
_inputFilename.c_str());
|
|
_recording = false;
|
|
delete[] _recordingBuffer;
|
|
_recordingBuffer = NULL;
|
|
return -1;
|
|
}
|
|
|
|
_ptrThreadRec.reset(new rtc::PlatformThread(
|
|
RecThreadFunc, this, "webrtc_audio_module_capture_thread"));
|
|
|
|
_ptrThreadRec->Start();
|
|
_ptrThreadRec->SetPriority(rtc::kRealtimePriority);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int32_t FileAudioDevice::StopRecording() {
|
|
{
|
|
CriticalSectionScoped lock(&_critSect);
|
|
_recording = false;
|
|
}
|
|
|
|
if (_ptrThreadRec) {
|
|
_ptrThreadRec->Stop();
|
|
_ptrThreadRec.reset();
|
|
}
|
|
|
|
CriticalSectionScoped lock(&_critSect);
|
|
_recordingFramesLeft = 0;
|
|
if (_recordingBuffer) {
|
|
delete [] _recordingBuffer;
|
|
_recordingBuffer = NULL;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
bool FileAudioDevice::Recording() const {
|
|
return _recording;
|
|
}
|
|
|
|
int32_t FileAudioDevice::SetAGC(bool enable) { return -1; }
|
|
|
|
bool FileAudioDevice::AGC() const { return false; }
|
|
|
|
int32_t FileAudioDevice::SetWaveOutVolume(uint16_t volumeLeft,
|
|
uint16_t volumeRight) {
|
|
return -1;
|
|
}
|
|
|
|
int32_t FileAudioDevice::WaveOutVolume(uint16_t& volumeLeft,
|
|
uint16_t& volumeRight) const {
|
|
return -1;
|
|
}
|
|
|
|
int32_t FileAudioDevice::InitSpeaker() { return -1; }
|
|
|
|
bool FileAudioDevice::SpeakerIsInitialized() const { return false; }
|
|
|
|
int32_t FileAudioDevice::InitMicrophone() { return 0; }
|
|
|
|
bool FileAudioDevice::MicrophoneIsInitialized() const { return true; }
|
|
|
|
int32_t FileAudioDevice::SpeakerVolumeIsAvailable(bool& available) {
|
|
return -1;
|
|
}
|
|
|
|
int32_t FileAudioDevice::SetSpeakerVolume(uint32_t volume) { return -1; }
|
|
|
|
int32_t FileAudioDevice::SpeakerVolume(uint32_t& volume) const { return -1; }
|
|
|
|
int32_t FileAudioDevice::MaxSpeakerVolume(uint32_t& maxVolume) const {
|
|
return -1;
|
|
}
|
|
|
|
int32_t FileAudioDevice::MinSpeakerVolume(uint32_t& minVolume) const {
|
|
return -1;
|
|
}
|
|
|
|
int32_t FileAudioDevice::SpeakerVolumeStepSize(uint16_t& stepSize) const {
|
|
return -1;
|
|
}
|
|
|
|
int32_t FileAudioDevice::MicrophoneVolumeIsAvailable(bool& available) {
|
|
return -1;
|
|
}
|
|
|
|
int32_t FileAudioDevice::SetMicrophoneVolume(uint32_t volume) { return -1; }
|
|
|
|
int32_t FileAudioDevice::MicrophoneVolume(uint32_t& volume) const {
|
|
return -1;
|
|
}
|
|
|
|
int32_t FileAudioDevice::MaxMicrophoneVolume(uint32_t& maxVolume) const {
|
|
return -1;
|
|
}
|
|
|
|
int32_t FileAudioDevice::MinMicrophoneVolume(uint32_t& minVolume) const {
|
|
return -1;
|
|
}
|
|
|
|
int32_t FileAudioDevice::MicrophoneVolumeStepSize(uint16_t& stepSize) const {
|
|
return -1;
|
|
}
|
|
|
|
int32_t FileAudioDevice::SpeakerMuteIsAvailable(bool& available) { return -1; }
|
|
|
|
int32_t FileAudioDevice::SetSpeakerMute(bool enable) { return -1; }
|
|
|
|
int32_t FileAudioDevice::SpeakerMute(bool& enabled) const { return -1; }
|
|
|
|
int32_t FileAudioDevice::MicrophoneMuteIsAvailable(bool& available) {
|
|
return -1;
|
|
}
|
|
|
|
int32_t FileAudioDevice::SetMicrophoneMute(bool enable) { return -1; }
|
|
|
|
int32_t FileAudioDevice::MicrophoneMute(bool& enabled) const { return -1; }
|
|
|
|
int32_t FileAudioDevice::MicrophoneBoostIsAvailable(bool& available) {
|
|
return -1;
|
|
}
|
|
|
|
int32_t FileAudioDevice::SetMicrophoneBoost(bool enable) { return -1; }
|
|
|
|
int32_t FileAudioDevice::MicrophoneBoost(bool& enabled) const { return -1; }
|
|
|
|
int32_t FileAudioDevice::StereoPlayoutIsAvailable(bool& available) {
|
|
available = true;
|
|
return 0;
|
|
}
|
|
int32_t FileAudioDevice::SetStereoPlayout(bool enable) {
|
|
return 0;
|
|
}
|
|
|
|
int32_t FileAudioDevice::StereoPlayout(bool& enabled) const {
|
|
enabled = true;
|
|
return 0;
|
|
}
|
|
|
|
int32_t FileAudioDevice::StereoRecordingIsAvailable(bool& available) {
|
|
available = true;
|
|
return 0;
|
|
}
|
|
|
|
int32_t FileAudioDevice::SetStereoRecording(bool enable) {
|
|
return 0;
|
|
}
|
|
|
|
int32_t FileAudioDevice::StereoRecording(bool& enabled) const {
|
|
enabled = true;
|
|
return 0;
|
|
}
|
|
|
|
int32_t FileAudioDevice::SetPlayoutBuffer(
|
|
const AudioDeviceModule::BufferType type,
|
|
uint16_t sizeMS) {
|
|
return 0;
|
|
}
|
|
|
|
int32_t FileAudioDevice::PlayoutBuffer(AudioDeviceModule::BufferType& type,
|
|
uint16_t& sizeMS) const {
|
|
type = _playBufType;
|
|
return 0;
|
|
}
|
|
|
|
int32_t FileAudioDevice::PlayoutDelay(uint16_t& delayMS) const {
|
|
return 0;
|
|
}
|
|
|
|
int32_t FileAudioDevice::RecordingDelay(uint16_t& delayMS) const { return -1; }
|
|
|
|
int32_t FileAudioDevice::CPULoad(uint16_t& load) const { return -1; }
|
|
|
|
bool FileAudioDevice::PlayoutWarning() const { return false; }
|
|
|
|
bool FileAudioDevice::PlayoutError() const { return false; }
|
|
|
|
bool FileAudioDevice::RecordingWarning() const { return false; }
|
|
|
|
bool FileAudioDevice::RecordingError() const { return false; }
|
|
|
|
void FileAudioDevice::ClearPlayoutWarning() {}
|
|
|
|
void FileAudioDevice::ClearPlayoutError() {}
|
|
|
|
void FileAudioDevice::ClearRecordingWarning() {}
|
|
|
|
void FileAudioDevice::ClearRecordingError() {}
|
|
|
|
void FileAudioDevice::AttachAudioBuffer(AudioDeviceBuffer* audioBuffer) {
|
|
CriticalSectionScoped lock(&_critSect);
|
|
|
|
_ptrAudioBuffer = audioBuffer;
|
|
|
|
// Inform the AudioBuffer about default settings for this implementation.
|
|
// Set all values to zero here since the actual settings will be done by
|
|
// InitPlayout and InitRecording later.
|
|
_ptrAudioBuffer->SetRecordingSampleRate(0);
|
|
_ptrAudioBuffer->SetPlayoutSampleRate(0);
|
|
_ptrAudioBuffer->SetRecordingChannels(0);
|
|
_ptrAudioBuffer->SetPlayoutChannels(0);
|
|
}
|
|
|
|
bool FileAudioDevice::PlayThreadFunc(void* pThis)
|
|
{
|
|
return (static_cast<FileAudioDevice*>(pThis)->PlayThreadProcess());
|
|
}
|
|
|
|
bool FileAudioDevice::RecThreadFunc(void* pThis)
|
|
{
|
|
return (static_cast<FileAudioDevice*>(pThis)->RecThreadProcess());
|
|
}
|
|
|
|
bool FileAudioDevice::PlayThreadProcess()
|
|
{
|
|
if(!_playing) {
|
|
return false;
|
|
}
|
|
uint64_t currentTime = _clock->CurrentNtpInMilliseconds();
|
|
_critSect.Enter();
|
|
|
|
if (_lastCallPlayoutMillis == 0 ||
|
|
currentTime - _lastCallPlayoutMillis >= 10) {
|
|
_critSect.Leave();
|
|
_ptrAudioBuffer->RequestPlayoutData(_playoutFramesIn10MS);
|
|
_critSect.Enter();
|
|
|
|
_playoutFramesLeft = _ptrAudioBuffer->GetPlayoutData(_playoutBuffer);
|
|
assert(_playoutFramesLeft == _playoutFramesIn10MS);
|
|
if (_outputFile.is_open()) {
|
|
_outputFile.Write(_playoutBuffer, kPlayoutBufferSize);
|
|
}
|
|
_lastCallPlayoutMillis = currentTime;
|
|
}
|
|
_playoutFramesLeft = 0;
|
|
_critSect.Leave();
|
|
|
|
uint64_t deltaTimeMillis = _clock->CurrentNtpInMilliseconds() - currentTime;
|
|
if(deltaTimeMillis < 10) {
|
|
SleepMs(10 - deltaTimeMillis);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool FileAudioDevice::RecThreadProcess()
|
|
{
|
|
if (!_recording) {
|
|
return false;
|
|
}
|
|
|
|
uint64_t currentTime = _clock->CurrentNtpInMilliseconds();
|
|
_critSect.Enter();
|
|
|
|
if (_lastCallRecordMillis == 0 ||
|
|
currentTime - _lastCallRecordMillis >= 10) {
|
|
if (_inputFile.is_open()) {
|
|
if (_inputFile.Read(_recordingBuffer, kRecordingBufferSize) > 0) {
|
|
_ptrAudioBuffer->SetRecordedBuffer(_recordingBuffer,
|
|
_recordingFramesIn10MS);
|
|
} else {
|
|
_inputFile.Rewind();
|
|
}
|
|
_lastCallRecordMillis = currentTime;
|
|
_critSect.Leave();
|
|
_ptrAudioBuffer->DeliverRecordedData();
|
|
_critSect.Enter();
|
|
}
|
|
}
|
|
|
|
_critSect.Leave();
|
|
|
|
uint64_t deltaTimeMillis = _clock->CurrentNtpInMilliseconds() - currentTime;
|
|
if(deltaTimeMillis < 10) {
|
|
SleepMs(10 - deltaTimeMillis);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
} // namespace webrtc
|