NOPRESUBMIT=True # cpplint errors that aren't caused by this CL. NOTRY=True NOTREECHECKS=True TBR=kwiberg@webrtc.org, kjellander@webrtc.org Bug: webrtc:7634 Change-Id: I3cca0fbaa807b563c95979cccd6d1bec32055f36 Reviewed-on: https://chromium-review.googlesource.com/562156 Commit-Queue: Edward Lemur <ehmaldonado@webrtc.org> Reviewed-by: Mirko Bonadei <mbonadei@webrtc.org> Cr-Commit-Position: refs/heads/master@{#18919}
393 lines
12 KiB
C++
393 lines
12 KiB
C++
/*
|
|
* Copyright (c) 2012 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/voice_engine/output_mixer.h"
|
|
|
|
#include "webrtc/modules/audio_processing/include/audio_processing.h"
|
|
#include "webrtc/rtc_base/format_macros.h"
|
|
#include "webrtc/system_wrappers/include/file_wrapper.h"
|
|
#include "webrtc/system_wrappers/include/trace.h"
|
|
#include "webrtc/voice_engine/statistics.h"
|
|
#include "webrtc/voice_engine/utility.h"
|
|
|
|
namespace webrtc {
|
|
namespace voe {
|
|
|
|
void
|
|
OutputMixer::NewMixedAudio(int32_t id,
|
|
const AudioFrame& generalAudioFrame,
|
|
const AudioFrame** uniqueAudioFrames,
|
|
uint32_t size)
|
|
{
|
|
WEBRTC_TRACE(kTraceStream, kTraceVoice, VoEId(_instanceId,-1),
|
|
"OutputMixer::NewMixedAudio(id=%d, size=%u)", id, size);
|
|
|
|
_audioFrame.CopyFrom(generalAudioFrame);
|
|
_audioFrame.id_ = id;
|
|
}
|
|
|
|
void OutputMixer::PlayNotification(int32_t id, uint32_t durationMs)
|
|
{
|
|
WEBRTC_TRACE(kTraceStream, kTraceVoice, VoEId(_instanceId,-1),
|
|
"OutputMixer::PlayNotification(id=%d, durationMs=%d)",
|
|
id, durationMs);
|
|
// Not implement yet
|
|
}
|
|
|
|
void OutputMixer::RecordNotification(int32_t id,
|
|
uint32_t durationMs)
|
|
{
|
|
WEBRTC_TRACE(kTraceStream, kTraceVoice, VoEId(_instanceId,-1),
|
|
"OutputMixer::RecordNotification(id=%d, durationMs=%d)",
|
|
id, durationMs);
|
|
|
|
// Not implement yet
|
|
}
|
|
|
|
void OutputMixer::PlayFileEnded(int32_t id)
|
|
{
|
|
WEBRTC_TRACE(kTraceStream, kTraceVoice, VoEId(_instanceId,-1),
|
|
"OutputMixer::PlayFileEnded(id=%d)", id);
|
|
|
|
// not needed
|
|
}
|
|
|
|
void OutputMixer::RecordFileEnded(int32_t id)
|
|
{
|
|
WEBRTC_TRACE(kTraceStream, kTraceVoice, VoEId(_instanceId,-1),
|
|
"OutputMixer::RecordFileEnded(id=%d)", id);
|
|
assert(id == _instanceId);
|
|
|
|
rtc::CritScope cs(&_fileCritSect);
|
|
_outputFileRecording = false;
|
|
WEBRTC_TRACE(kTraceStateInfo, kTraceVoice, VoEId(_instanceId,-1),
|
|
"OutputMixer::RecordFileEnded() =>"
|
|
"output file recorder module is shutdown");
|
|
}
|
|
|
|
int32_t
|
|
OutputMixer::Create(OutputMixer*& mixer, uint32_t instanceId)
|
|
{
|
|
WEBRTC_TRACE(kTraceMemory, kTraceVoice, instanceId,
|
|
"OutputMixer::Create(instanceId=%d)", instanceId);
|
|
mixer = new OutputMixer(instanceId);
|
|
if (mixer == NULL)
|
|
{
|
|
WEBRTC_TRACE(kTraceMemory, kTraceVoice, instanceId,
|
|
"OutputMixer::Create() unable to allocate memory for"
|
|
"mixer");
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
OutputMixer::OutputMixer(uint32_t instanceId) :
|
|
_mixerModule(*AudioConferenceMixer::Create(instanceId)),
|
|
_instanceId(instanceId),
|
|
_mixingFrequencyHz(8000),
|
|
_outputFileRecording(false)
|
|
{
|
|
WEBRTC_TRACE(kTraceMemory, kTraceVoice, VoEId(_instanceId,-1),
|
|
"OutputMixer::OutputMixer() - ctor");
|
|
|
|
if (_mixerModule.RegisterMixedStreamCallback(this) == -1)
|
|
{
|
|
WEBRTC_TRACE(kTraceError, kTraceVoice, VoEId(_instanceId,-1),
|
|
"OutputMixer::OutputMixer() failed to register mixer"
|
|
"callbacks");
|
|
}
|
|
}
|
|
|
|
void
|
|
OutputMixer::Destroy(OutputMixer*& mixer)
|
|
{
|
|
if (mixer)
|
|
{
|
|
delete mixer;
|
|
mixer = NULL;
|
|
}
|
|
}
|
|
|
|
OutputMixer::~OutputMixer()
|
|
{
|
|
WEBRTC_TRACE(kTraceMemory, kTraceVoice, VoEId(_instanceId,-1),
|
|
"OutputMixer::~OutputMixer() - dtor");
|
|
{
|
|
rtc::CritScope cs(&_fileCritSect);
|
|
if (output_file_recorder_) {
|
|
output_file_recorder_->RegisterModuleFileCallback(NULL);
|
|
output_file_recorder_->StopRecording();
|
|
}
|
|
}
|
|
_mixerModule.UnRegisterMixedStreamCallback();
|
|
delete &_mixerModule;
|
|
}
|
|
|
|
int32_t
|
|
OutputMixer::SetEngineInformation(voe::Statistics& engineStatistics)
|
|
{
|
|
WEBRTC_TRACE(kTraceInfo, kTraceVoice, VoEId(_instanceId,-1),
|
|
"OutputMixer::SetEngineInformation()");
|
|
_engineStatisticsPtr = &engineStatistics;
|
|
return 0;
|
|
}
|
|
|
|
int32_t
|
|
OutputMixer::SetAudioProcessingModule(AudioProcessing* audioProcessingModule)
|
|
{
|
|
WEBRTC_TRACE(kTraceInfo, kTraceVoice, VoEId(_instanceId,-1),
|
|
"OutputMixer::SetAudioProcessingModule("
|
|
"audioProcessingModule=0x%x)", audioProcessingModule);
|
|
_audioProcessingModulePtr = audioProcessingModule;
|
|
return 0;
|
|
}
|
|
|
|
int32_t
|
|
OutputMixer::SetMixabilityStatus(MixerParticipant& participant,
|
|
bool mixable)
|
|
{
|
|
return _mixerModule.SetMixabilityStatus(&participant, mixable);
|
|
}
|
|
|
|
int32_t
|
|
OutputMixer::SetAnonymousMixabilityStatus(MixerParticipant& participant,
|
|
bool mixable)
|
|
{
|
|
return _mixerModule.SetAnonymousMixabilityStatus(&participant, mixable);
|
|
}
|
|
|
|
int32_t
|
|
OutputMixer::MixActiveChannels()
|
|
{
|
|
_mixerModule.Process();
|
|
return 0;
|
|
}
|
|
|
|
int OutputMixer::StartRecordingPlayout(const char* fileName,
|
|
const CodecInst* codecInst)
|
|
{
|
|
WEBRTC_TRACE(kTraceInfo, kTraceVoice, VoEId(_instanceId,-1),
|
|
"OutputMixer::StartRecordingPlayout(fileName=%s)", fileName);
|
|
|
|
if (_outputFileRecording)
|
|
{
|
|
WEBRTC_TRACE(kTraceWarning, kTraceVoice, VoEId(_instanceId,-1),
|
|
"StartRecordingPlayout() is already recording");
|
|
return 0;
|
|
}
|
|
|
|
FileFormats format;
|
|
const uint32_t notificationTime(0);
|
|
CodecInst dummyCodec={100,"L16",16000,320,1,320000};
|
|
|
|
if ((codecInst != NULL) &&
|
|
((codecInst->channels < 1) || (codecInst->channels > 2)))
|
|
{
|
|
_engineStatisticsPtr->SetLastError(
|
|
VE_BAD_ARGUMENT, kTraceError,
|
|
"StartRecordingPlayout() invalid compression");
|
|
return(-1);
|
|
}
|
|
if(codecInst == NULL)
|
|
{
|
|
format = kFileFormatPcm16kHzFile;
|
|
codecInst=&dummyCodec;
|
|
}
|
|
else if((STR_CASE_CMP(codecInst->plname,"L16") == 0) ||
|
|
(STR_CASE_CMP(codecInst->plname,"PCMU") == 0) ||
|
|
(STR_CASE_CMP(codecInst->plname,"PCMA") == 0))
|
|
{
|
|
format = kFileFormatWavFile;
|
|
}
|
|
else
|
|
{
|
|
format = kFileFormatCompressedFile;
|
|
}
|
|
|
|
rtc::CritScope cs(&_fileCritSect);
|
|
|
|
// Destroy the old instance
|
|
if (output_file_recorder_) {
|
|
output_file_recorder_->RegisterModuleFileCallback(NULL);
|
|
output_file_recorder_.reset();
|
|
}
|
|
|
|
output_file_recorder_ = FileRecorder::CreateFileRecorder(
|
|
_instanceId, (const FileFormats)format);
|
|
if (!output_file_recorder_) {
|
|
_engineStatisticsPtr->SetLastError(
|
|
VE_INVALID_ARGUMENT, kTraceError,
|
|
"StartRecordingPlayout() fileRecorder format isnot correct");
|
|
return -1;
|
|
}
|
|
|
|
if (output_file_recorder_->StartRecordingAudioFile(
|
|
fileName, (const CodecInst&)*codecInst, notificationTime) != 0) {
|
|
_engineStatisticsPtr->SetLastError(
|
|
VE_BAD_FILE, kTraceError,
|
|
"StartRecordingAudioFile() failed to start file recording");
|
|
output_file_recorder_->StopRecording();
|
|
output_file_recorder_.reset();
|
|
return -1;
|
|
}
|
|
output_file_recorder_->RegisterModuleFileCallback(this);
|
|
_outputFileRecording = true;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int OutputMixer::StartRecordingPlayout(OutStream* stream,
|
|
const CodecInst* codecInst)
|
|
{
|
|
WEBRTC_TRACE(kTraceInfo, kTraceVoice, VoEId(_instanceId,-1),
|
|
"OutputMixer::StartRecordingPlayout()");
|
|
|
|
if (_outputFileRecording)
|
|
{
|
|
WEBRTC_TRACE(kTraceWarning, kTraceVoice, VoEId(_instanceId,-1),
|
|
"StartRecordingPlayout() is already recording");
|
|
return 0;
|
|
}
|
|
|
|
FileFormats format;
|
|
const uint32_t notificationTime(0);
|
|
CodecInst dummyCodec={100,"L16",16000,320,1,320000};
|
|
|
|
if (codecInst != NULL && codecInst->channels != 1)
|
|
{
|
|
_engineStatisticsPtr->SetLastError(
|
|
VE_BAD_ARGUMENT, kTraceError,
|
|
"StartRecordingPlayout() invalid compression");
|
|
return(-1);
|
|
}
|
|
if(codecInst == NULL)
|
|
{
|
|
format = kFileFormatPcm16kHzFile;
|
|
codecInst=&dummyCodec;
|
|
}
|
|
else if((STR_CASE_CMP(codecInst->plname,"L16") == 0) ||
|
|
(STR_CASE_CMP(codecInst->plname,"PCMU") == 0) ||
|
|
(STR_CASE_CMP(codecInst->plname,"PCMA") == 0))
|
|
{
|
|
format = kFileFormatWavFile;
|
|
}
|
|
else
|
|
{
|
|
format = kFileFormatCompressedFile;
|
|
}
|
|
|
|
rtc::CritScope cs(&_fileCritSect);
|
|
|
|
// Destroy the old instance
|
|
if (output_file_recorder_) {
|
|
output_file_recorder_->RegisterModuleFileCallback(NULL);
|
|
output_file_recorder_.reset();
|
|
}
|
|
|
|
output_file_recorder_ = FileRecorder::CreateFileRecorder(
|
|
_instanceId, (const FileFormats)format);
|
|
if (!output_file_recorder_) {
|
|
_engineStatisticsPtr->SetLastError(
|
|
VE_INVALID_ARGUMENT, kTraceError,
|
|
"StartRecordingPlayout() fileRecorder format isnot correct");
|
|
return -1;
|
|
}
|
|
|
|
if (output_file_recorder_->StartRecordingAudioFile(stream, *codecInst,
|
|
notificationTime) != 0) {
|
|
_engineStatisticsPtr->SetLastError(
|
|
VE_BAD_FILE, kTraceError,
|
|
"StartRecordingAudioFile() failed to start file recording");
|
|
output_file_recorder_->StopRecording();
|
|
output_file_recorder_.reset();
|
|
return -1;
|
|
}
|
|
|
|
output_file_recorder_->RegisterModuleFileCallback(this);
|
|
_outputFileRecording = true;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int OutputMixer::StopRecordingPlayout()
|
|
{
|
|
WEBRTC_TRACE(kTraceInfo, kTraceVoice, VoEId(_instanceId,-1),
|
|
"OutputMixer::StopRecordingPlayout()");
|
|
|
|
if (!_outputFileRecording)
|
|
{
|
|
WEBRTC_TRACE(kTraceError, kTraceVoice, VoEId(_instanceId,-1),
|
|
"StopRecordingPlayout() file isnot recording");
|
|
return -1;
|
|
}
|
|
|
|
rtc::CritScope cs(&_fileCritSect);
|
|
|
|
if (output_file_recorder_->StopRecording() != 0) {
|
|
_engineStatisticsPtr->SetLastError(
|
|
VE_STOP_RECORDING_FAILED, kTraceError,
|
|
"StopRecording(), could not stop recording");
|
|
return -1;
|
|
}
|
|
output_file_recorder_->RegisterModuleFileCallback(NULL);
|
|
output_file_recorder_.reset();
|
|
_outputFileRecording = false;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int OutputMixer::GetMixedAudio(int sample_rate_hz,
|
|
size_t num_channels,
|
|
AudioFrame* frame) {
|
|
WEBRTC_TRACE(
|
|
kTraceStream, kTraceVoice, VoEId(_instanceId,-1),
|
|
"OutputMixer::GetMixedAudio(sample_rate_hz=%d, num_channels=%" PRIuS ")",
|
|
sample_rate_hz, num_channels);
|
|
|
|
// --- Record playout if enabled
|
|
{
|
|
rtc::CritScope cs(&_fileCritSect);
|
|
if (_outputFileRecording && output_file_recorder_)
|
|
output_file_recorder_->RecordAudioToFile(_audioFrame);
|
|
}
|
|
|
|
frame->num_channels_ = num_channels;
|
|
frame->sample_rate_hz_ = sample_rate_hz;
|
|
// TODO(andrew): Ideally the downmixing would occur much earlier, in
|
|
// AudioCodingModule.
|
|
RemixAndResample(_audioFrame, &resampler_, frame);
|
|
return 0;
|
|
}
|
|
|
|
int32_t
|
|
OutputMixer::DoOperationsOnCombinedSignal(bool feed_data_to_apm)
|
|
{
|
|
if (_audioFrame.sample_rate_hz_ != _mixingFrequencyHz)
|
|
{
|
|
WEBRTC_TRACE(kTraceStream, kTraceVoice, VoEId(_instanceId,-1),
|
|
"OutputMixer::DoOperationsOnCombinedSignal() => "
|
|
"mixing frequency = %d", _audioFrame.sample_rate_hz_);
|
|
_mixingFrequencyHz = _audioFrame.sample_rate_hz_;
|
|
}
|
|
|
|
// --- Far-end Voice Quality Enhancement (AudioProcessing Module)
|
|
if (feed_data_to_apm) {
|
|
if (_audioProcessingModulePtr->ProcessReverseStream(&_audioFrame) != 0) {
|
|
WEBRTC_TRACE(kTraceWarning, kTraceVoice, VoEId(_instanceId, -1),
|
|
"AudioProcessingModule::ProcessReverseStream() => error");
|
|
RTC_NOTREACHED();
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
} // namespace voe
|
|
} // namespace webrtc
|