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}
261 lines
8.8 KiB
C++
261 lines
8.8 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/file_recorder.h"
|
|
|
|
#include <list>
|
|
|
|
#include "webrtc/audio/utility/audio_frame_operations.h"
|
|
#include "webrtc/common_audio/resampler/include/resampler.h"
|
|
#include "webrtc/common_types.h"
|
|
#include "webrtc/modules/include/module_common_types.h"
|
|
#include "webrtc/modules/media_file/media_file.h"
|
|
#include "webrtc/modules/media_file/media_file_defines.h"
|
|
#include "webrtc/rtc_base/logging.h"
|
|
#include "webrtc/rtc_base/platform_thread.h"
|
|
#include "webrtc/system_wrappers/include/event_wrapper.h"
|
|
#include "webrtc/typedefs.h"
|
|
#include "webrtc/voice_engine/coder.h"
|
|
|
|
namespace webrtc {
|
|
|
|
namespace {
|
|
|
|
// The largest decoded frame size in samples (60ms with 32kHz sample rate).
|
|
enum { MAX_AUDIO_BUFFER_IN_SAMPLES = 60 * 32 };
|
|
enum { MAX_AUDIO_BUFFER_IN_BYTES = MAX_AUDIO_BUFFER_IN_SAMPLES * 2 };
|
|
enum { kMaxAudioBufferQueueLength = 100 };
|
|
|
|
class FileRecorderImpl : public FileRecorder {
|
|
public:
|
|
FileRecorderImpl(uint32_t instanceID, FileFormats fileFormat);
|
|
~FileRecorderImpl() override;
|
|
|
|
// FileRecorder functions.
|
|
int32_t RegisterModuleFileCallback(FileCallback* callback) override;
|
|
FileFormats RecordingFileFormat() const override;
|
|
int32_t StartRecordingAudioFile(const char* fileName,
|
|
const CodecInst& codecInst,
|
|
uint32_t notificationTimeMs) override;
|
|
int32_t StartRecordingAudioFile(OutStream* destStream,
|
|
const CodecInst& codecInst,
|
|
uint32_t notificationTimeMs) override;
|
|
int32_t StopRecording() override;
|
|
bool IsRecording() const override;
|
|
int32_t codec_info(CodecInst* codecInst) const override;
|
|
int32_t RecordAudioToFile(const AudioFrame& frame) override;
|
|
|
|
private:
|
|
int32_t WriteEncodedAudioData(const int8_t* audioBuffer, size_t bufferLength);
|
|
|
|
int32_t SetUpAudioEncoder();
|
|
|
|
uint32_t _instanceID;
|
|
FileFormats _fileFormat;
|
|
MediaFile* _moduleFile;
|
|
|
|
CodecInst codec_info_;
|
|
int8_t _audioBuffer[MAX_AUDIO_BUFFER_IN_BYTES];
|
|
AudioCoder _audioEncoder;
|
|
Resampler _audioResampler;
|
|
};
|
|
|
|
FileRecorderImpl::FileRecorderImpl(uint32_t instanceID, FileFormats fileFormat)
|
|
: _instanceID(instanceID),
|
|
_fileFormat(fileFormat),
|
|
_moduleFile(MediaFile::CreateMediaFile(_instanceID)),
|
|
codec_info_(),
|
|
_audioBuffer(),
|
|
_audioEncoder(instanceID),
|
|
_audioResampler() {}
|
|
|
|
FileRecorderImpl::~FileRecorderImpl() {
|
|
MediaFile::DestroyMediaFile(_moduleFile);
|
|
}
|
|
|
|
FileFormats FileRecorderImpl::RecordingFileFormat() const {
|
|
return _fileFormat;
|
|
}
|
|
|
|
int32_t FileRecorderImpl::RegisterModuleFileCallback(FileCallback* callback) {
|
|
if (_moduleFile == NULL) {
|
|
return -1;
|
|
}
|
|
return _moduleFile->SetModuleFileCallback(callback);
|
|
}
|
|
|
|
int32_t FileRecorderImpl::StartRecordingAudioFile(const char* fileName,
|
|
const CodecInst& codecInst,
|
|
uint32_t notificationTimeMs) {
|
|
if (_moduleFile == NULL) {
|
|
return -1;
|
|
}
|
|
codec_info_ = codecInst;
|
|
int32_t retVal = 0;
|
|
retVal = _moduleFile->StartRecordingAudioFile(fileName, _fileFormat,
|
|
codecInst, notificationTimeMs);
|
|
|
|
if (retVal == 0) {
|
|
retVal = SetUpAudioEncoder();
|
|
}
|
|
if (retVal != 0) {
|
|
LOG(LS_WARNING) << "Failed to initialize file " << fileName
|
|
<< " for recording.";
|
|
|
|
if (IsRecording()) {
|
|
StopRecording();
|
|
}
|
|
}
|
|
return retVal;
|
|
}
|
|
|
|
int32_t FileRecorderImpl::StartRecordingAudioFile(OutStream* destStream,
|
|
const CodecInst& codecInst,
|
|
uint32_t notificationTimeMs) {
|
|
codec_info_ = codecInst;
|
|
int32_t retVal = _moduleFile->StartRecordingAudioStream(
|
|
*destStream, _fileFormat, codecInst, notificationTimeMs);
|
|
|
|
if (retVal == 0) {
|
|
retVal = SetUpAudioEncoder();
|
|
}
|
|
if (retVal != 0) {
|
|
LOG(LS_WARNING) << "Failed to initialize outStream for recording.";
|
|
|
|
if (IsRecording()) {
|
|
StopRecording();
|
|
}
|
|
}
|
|
return retVal;
|
|
}
|
|
|
|
int32_t FileRecorderImpl::StopRecording() {
|
|
memset(&codec_info_, 0, sizeof(CodecInst));
|
|
return _moduleFile->StopRecording();
|
|
}
|
|
|
|
bool FileRecorderImpl::IsRecording() const {
|
|
return _moduleFile->IsRecording();
|
|
}
|
|
|
|
int32_t FileRecorderImpl::RecordAudioToFile(
|
|
const AudioFrame& incomingAudioFrame) {
|
|
if (codec_info_.plfreq == 0) {
|
|
LOG(LS_WARNING) << "RecordAudioToFile() recording audio is not "
|
|
<< "turned on.";
|
|
return -1;
|
|
}
|
|
AudioFrame tempAudioFrame;
|
|
tempAudioFrame.samples_per_channel_ = 0;
|
|
if (incomingAudioFrame.num_channels_ == 2 && !_moduleFile->IsStereo()) {
|
|
// Recording mono but incoming audio is (interleaved) stereo.
|
|
tempAudioFrame.num_channels_ = 1;
|
|
tempAudioFrame.sample_rate_hz_ = incomingAudioFrame.sample_rate_hz_;
|
|
tempAudioFrame.samples_per_channel_ =
|
|
incomingAudioFrame.samples_per_channel_;
|
|
if (!incomingAudioFrame.muted()) {
|
|
AudioFrameOperations::StereoToMono(
|
|
incomingAudioFrame.data(), incomingAudioFrame.samples_per_channel_,
|
|
tempAudioFrame.mutable_data());
|
|
}
|
|
} else if (incomingAudioFrame.num_channels_ == 1 && _moduleFile->IsStereo()) {
|
|
// Recording stereo but incoming audio is mono.
|
|
tempAudioFrame.num_channels_ = 2;
|
|
tempAudioFrame.sample_rate_hz_ = incomingAudioFrame.sample_rate_hz_;
|
|
tempAudioFrame.samples_per_channel_ =
|
|
incomingAudioFrame.samples_per_channel_;
|
|
if (!incomingAudioFrame.muted()) {
|
|
AudioFrameOperations::MonoToStereo(
|
|
incomingAudioFrame.data(), incomingAudioFrame.samples_per_channel_,
|
|
tempAudioFrame.mutable_data());
|
|
}
|
|
}
|
|
|
|
const AudioFrame* ptrAudioFrame = &incomingAudioFrame;
|
|
if (tempAudioFrame.samples_per_channel_ != 0) {
|
|
// If ptrAudioFrame is not empty it contains the audio to be recorded.
|
|
ptrAudioFrame = &tempAudioFrame;
|
|
}
|
|
|
|
// Encode the audio data before writing to file. Don't encode if the codec
|
|
// is PCM.
|
|
// NOTE: stereo recording is only supported for WAV files.
|
|
// TODO(hellner): WAV expect PCM in little endian byte order. Not
|
|
// "encoding" with PCM coder should be a problem for big endian systems.
|
|
size_t encodedLenInBytes = 0;
|
|
if (_fileFormat == kFileFormatPreencodedFile ||
|
|
STR_CASE_CMP(codec_info_.plname, "L16") != 0) {
|
|
if (_audioEncoder.Encode(*ptrAudioFrame, _audioBuffer,
|
|
&encodedLenInBytes) == -1) {
|
|
LOG(LS_WARNING) << "RecordAudioToFile() codec " << codec_info_.plname
|
|
<< " not supported or failed to encode stream.";
|
|
return -1;
|
|
}
|
|
} else {
|
|
size_t outLen = 0;
|
|
_audioResampler.ResetIfNeeded(ptrAudioFrame->sample_rate_hz_,
|
|
codec_info_.plfreq,
|
|
ptrAudioFrame->num_channels_);
|
|
// TODO(yujo): skip resample if frame is muted.
|
|
_audioResampler.Push(
|
|
ptrAudioFrame->data(),
|
|
ptrAudioFrame->samples_per_channel_ * ptrAudioFrame->num_channels_,
|
|
reinterpret_cast<int16_t*>(_audioBuffer), MAX_AUDIO_BUFFER_IN_BYTES,
|
|
outLen);
|
|
encodedLenInBytes = outLen * sizeof(int16_t);
|
|
}
|
|
|
|
// Codec may not be operating at a frame rate of 10 ms. Whenever enough
|
|
// 10 ms chunks of data has been pushed to the encoder an encoded frame
|
|
// will be available. Wait until then.
|
|
if (encodedLenInBytes) {
|
|
if (WriteEncodedAudioData(_audioBuffer, encodedLenInBytes) == -1) {
|
|
return -1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int32_t FileRecorderImpl::SetUpAudioEncoder() {
|
|
if (_fileFormat == kFileFormatPreencodedFile ||
|
|
STR_CASE_CMP(codec_info_.plname, "L16") != 0) {
|
|
if (_audioEncoder.SetEncodeCodec(codec_info_) == -1) {
|
|
LOG(LS_ERROR) << "SetUpAudioEncoder() codec " << codec_info_.plname
|
|
<< " not supported.";
|
|
return -1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int32_t FileRecorderImpl::codec_info(CodecInst* codecInst) const {
|
|
if (codec_info_.plfreq == 0) {
|
|
return -1;
|
|
}
|
|
*codecInst = codec_info_;
|
|
return 0;
|
|
}
|
|
|
|
int32_t FileRecorderImpl::WriteEncodedAudioData(const int8_t* audioBuffer,
|
|
size_t bufferLength) {
|
|
return _moduleFile->IncomingAudioData(audioBuffer, bufferLength);
|
|
}
|
|
|
|
} // namespace
|
|
|
|
std::unique_ptr<FileRecorder> FileRecorder::CreateFileRecorder(
|
|
uint32_t instanceID,
|
|
FileFormats fileFormat) {
|
|
return std::unique_ptr<FileRecorder>(
|
|
new FileRecorderImpl(instanceID, fileFormat));
|
|
}
|
|
|
|
} // namespace webrtc
|