最近公司的iPad项目中一个功能点涉及到了VOIP通讯中的录音,需要在已有的WebRTC引擎中增加录音功能,录制通话双方的声音
参考了往上一位兄弟的博文(链接在此 http://blog.csdn.net/darkinger/article/details/13627479),代码实现基本问题不大,就是由于WebRTC本身版本更新导致部分代码要改动下结构;
但是那位兄台的代码存在两个问题
1 在一处地方没有描述清楚,导致混音不可用.后面仔细跟踪了下,调整下顺序即可.
2 录制出来的文件默认是PCM16K的裸数据,而不是通话使用的编码方式,在这里走了一天弯路(还得怪自己懒,其实去看下代码就知道了)

OK,以下是我做的全盘修改:

//////////////voe_file.h///////////////
基类增加两个虚函数接口,用于启停录音调用

    //DECWANG_4RECORD 20140814
virtual int StartRecordingPlayoutAndMic(const char* fileNameUTF8,
CodecInst* compression = NULL,
int maxSizeBytes = -1) = 0;
virtual int StopRecordingPlayoutAndMic() = 0;
/**/

//////////////voe_file_impl.h///////////////

子类定义增加两个虚函数接口,用于启停录音调用

    virtual int StartRecordingPlayoutAndMic(const char* fileNameUTF8,
CodecInst* compression = NULL,
int maxSizeBytes = -1);
virtual int StopRecordingPlayoutAndMic();

//////////////voe_file_impl.cc///////////////

int VoEFileImpl::StartRecordingPlayoutAndMic(const char* fileNameUTF8, CodecInst* compression,int maxSizeBytes)
{
//DECWANG_4RECORD,用于启动录音进程
WEBRTC_TRACE(kTraceApiCall, kTraceVoice, VoEId(_shared->instance_id(),-1),
"StartRecordingPlayoutAndMic(fileNameUTF8=%s, "
"compression, maxSizeBytes=%d)",
fileNameUTF8, maxSizeBytes);
assert(1024 == FileWrapper::kMaxFileNameSize); if (!_shared->statics()->Initialized())
{
_shared->statics()->SetLastError(VE_NOT_INITED, kTraceError);
return -1;
}
_shared->outputall_mixer()->StartRecordingPlayout(fileNameUTF8, compression);
return 0;
} int VoEFileImpl::StopRecordingPlayoutAndMic()
{
//DECWANG_4RECORD ,用于停止录音进程
WEBRTC_TRACE(kTraceApiCall, kTraceVoice, VoEId(_shared->instance_id(),-1),
"StopRecordingPlayoutAndMic");
if (!_shared->statics()->Initialized())
{
_shared->statics()->SetLastError(VE_NOT_INITED, kTraceError);
return -1;
}
return _shared->outputall_mixer()->StopRecordingPlayout();
}

//////////////share_data.h///////////////
增加一个处理双向数据的混音对象,并增加对应的对象访问接口

public:
//DECWANG_4RECORD
OutputMixer* outputall_mixer() { return _outputAllMixerPtr; }
Statistics* statics() {return &_engineStatistics;}
protected:
//DECWANG_4RECORD
OutputMixer* _outputAllMixerPtr; //for Record speaker+mic

//////////////share_data.cc///////////////
初始化

SharedData::SharedData() :
_instanceId(++_gInstanceCounter),
_apiCritPtr(CriticalSectionWrapper::CreateCriticalSection()),
_channelManager(_gInstanceCounter),
_engineStatistics(_gInstanceCounter),
_audioDevicePtr(NULL),
audioproc_(NULL),
_moduleProcessThreadPtr(ProcessThread::CreateProcessThread()),
_externalRecording(false),
_externalPlayout(false)
{
Trace::CreateTrace();
if (OutputMixer::Create(_outputMixerPtr, _gInstanceCounter) == 0)
{
_outputMixerPtr->SetEngineInformation(_engineStatistics);
}
if (TransmitMixer::Create(_transmitMixerPtr, _gInstanceCounter) == 0)
{
_transmitMixerPtr->SetEngineInformation(*_moduleProcessThreadPtr,
_engineStatistics,
_channelManager);
}
//DECWANG_4RECORD
if (OutputMixer::Create(_outputAllMixerPtr, _gInstanceCounter) == 0)
{
_outputAllMixerPtr->SetEngineInformation(_engineStatistics);
}
/**/ _audioDeviceLayer = AudioDeviceModule::kPlatformDefaultAudio;
}

//////////////audio_conference_mixer_defines.h///////////////
增加一个处理混音的数据类

//DECWANG_4RECORD
//for Record speaker+mic
//record mic or playout signal from OutputMixer output
class AudioFrameMixerPart:public MixerParticipant
{
public:
AudioFrameMixerPart();
void SetAudioFrame(AudioFrame &audioFrame);
uint16_t GetPayloadDataLengthInSamples();
public:
// From MixerParticipant
int32_t GetAudioFrame(const int32_t id,AudioFrame& audioFrame);
int32_t NeededFrequency(const int32_t id);
private:
AudioFrame _audioFrame;
};

//////////////audio_conference_mixer_impl.cc///////////////
实现混音数据类的必备接口,原有代码是在.h中实现,为了干净,干脆直接全部移到cc中

AudioFrameMixerPart::AudioFrameMixerPart()
{
}
void AudioFrameMixerPart::SetAudioFrame(AudioFrame &audioFrame)
{
_audioFrame.CopyFrom(audioFrame);
}
uint16_t AudioFrameMixerPart::GetPayloadDataLengthInSamples()
{
return _audioFrame.samples_per_channel_;
}
int32_t AudioFrameMixerPart::GetAudioFrame(const int32_t id,AudioFrame& audioFrame)
{
if (_audioFrame.samples_per_channel_ <= 0)
return -1; audioFrame.CopyFrom(_audioFrame);
return 0;
};
int32_t AudioFrameMixerPart::NeededFrequency(const int32_t id)
{
return _audioFrame.sample_rate_hz_;
}

//////////////output_mixer.h///////////////
增加一个共有成员函数,用于返回数据帧

public:
AudioFrame* GetAudioFrame();
//////////////output_mixer.cc///////////////
//DECWANG_4RECORD
AudioFrame* OutputMixer::GetAudioFrame()
{
return &_audioFrame;
}

//////////////transmit_mixer.h///////////////
增加成员函数

public:
AudioFrame* GetAudioFrame();
//////////////transmit_mixer.cc///////////////
AudioFrame* TransmitMixer::GetAudioFrame()
{
return &_audioFrame;
}

//////////////voe_base_impl.h///////////////
头文件引用,增加
#include "webrtc/modules/audio_conference_mixer/interface/audio_conference_mixer_defines.h"
私有成员

private:
AudioFrameMixerPart _afmTransmitMixer;
AudioFrameMixerPart _afmOutputMixer;
//////////////voe_base_impl.cc///////////////
int32_t VoEBaseImpl::RecordedDataIsAvailable(
const void* audioSamples,
uint32_t nSamples,
uint8_t nBytesPerSample,
uint8_t nChannels,
uint32_t samplesPerSec,
uint32_t totalDelayMS,
int32_t clockDrift,
uint32_t currentMicLevel,
bool keyPressed,
uint32_t& newMicLevel)
{
WEBRTC_TRACE(kTraceStream, kTraceVoice, VoEId(_shared->instance_id(), -1),
"VoEBaseImpl::RecordedDataIsAvailable(nSamples=%u, "
"nBytesPerSample=%u, nChannels=%u, samplesPerSec=%u, "
"totalDelayMS=%u, clockDrift=%d, currentMicLevel=%u)",
nSamples, nBytesPerSample, nChannels, samplesPerSec,
totalDelayMS, clockDrift, currentMicLevel);
newMicLevel = static_cast<uint32_t>(ProcessRecordedDataWithAPM(
NULL, 0, audioSamples, samplesPerSec, nChannels, nSamples,
totalDelayMS, clockDrift, currentMicLevel, keyPressed));
//for Record speaker+mic
//DECWANG_4RECORD,用于拷贝已有的音频帧,用于下一步的混音
_afmTransmitMixer.SetAudioFrame(*(_shared->transmit_mixer()->GetAudioFrame())); return 0;
} int32_t VoEBaseImpl::NeedMorePlayData(
uint32_t nSamples,
uint8_t nBytesPerSample,
uint8_t nChannels,
uint32_t samplesPerSec,
void* audioSamples,
uint32_t& nSamplesOut)
{
WEBRTC_TRACE(kTraceStream, kTraceVoice, VoEId(_shared->instance_id(), -1),
"VoEBaseImpl::NeedMorePlayData(nSamples=%u, "
"nBytesPerSample=%d, nChannels=%d, samplesPerSec=%u)",
nSamples, nBytesPerSample, nChannels, samplesPerSec); assert(_shared->output_mixer() != NULL); //DECWANG_4RECORD,获取音频帧,与资料出处的区别就在于此.
//for Record speaker+mic
_afmOutputMixer.SetAudioFrame(*(_shared->output_mixer()->GetAudioFrame()));
/**/ // TODO(andrew): if the device is running in mono, we should tell the mixer
// here so that it will only request mono from AudioCodingModule.
// Perform mixing of all active participants (channel-based mixing)
_shared->output_mixer()->MixActiveChannels(); // Additional operations on the combined signal
_shared->output_mixer()->DoOperationsOnCombinedSignal(); // Retrieve the final output mix (resampled to match the ADM)
_shared->output_mixer()->GetMixedAudio(samplesPerSec, nChannels,
&_audioFrame); assert(static_cast<int>(nSamples) == _audioFrame.samples_per_channel_);
assert(samplesPerSec ==
static_cast<uint32_t>(_audioFrame.sample_rate_hz_)); // Deliver audio (PCM) samples to the ADM
memcpy(
(int16_t*) audioSamples,
(const int16_t*) _audioFrame.data_,
sizeof(int16_t) * (_audioFrame.samples_per_channel_
* _audioFrame.num_channels_)); nSamplesOut = _audioFrame.samples_per_channel_;
//DECWANG_4RECORD,用于实际混音动作
//for Record speaker+mic
if (_afmOutputMixer.GetPayloadDataLengthInSamples() == _afmTransmitMixer.GetPayloadDataLengthInSamples())
{
AudioFrame audioFrameX;
_shared->outputall_mixer()->MixActiveChannels();
_shared->outputall_mixer()->DoOperationsOnCombinedSignal();
_shared->outputall_mixer()->GetMixedAudio(samplesPerSec, nChannels, &audioFrameX);
}
/**/
return 0;
}
int VoEBaseImpl::Init(AudioDeviceModule* external_adm,
AudioProcessing* audioproc)
{
WEBRTC_TRACE(kTraceApiCall, kTraceVoice, VoEId(_shared->instance_id(), -1),
"Init(external_adm=0x%p)", external_adm);
CriticalSectionScoped cs(_shared->crit_sec()); WebRtcSpl_Init(); if (_shared->statistics().Initialized())
{
return 0;
} if (_shared->process_thread())
{
if (_shared->process_thread()->Start() != 0)
{
_shared->SetLastError(VE_THREAD_ERROR, kTraceError,
"Init() failed to start module process thread");
return -1;
}
} // Create an internal ADM if the user has not added an external
// ADM implementation as input to Init().
if (external_adm == NULL)
{
// Create the internal ADM implementation.
_shared->set_audio_device(AudioDeviceModuleImpl::Create(
VoEId(_shared->instance_id(), -1), _shared->audio_device_layer())); if (_shared->audio_device() == NULL)
{
_shared->SetLastError(VE_NO_MEMORY, kTraceCritical,
"Init() failed to create the ADM");
return -1;
}
}
else
{
// Use the already existing external ADM implementation.
_shared->set_audio_device(external_adm);
WEBRTC_TRACE(kTraceInfo, kTraceVoice, VoEId(_shared->instance_id(), -1),
"An external ADM implementation will be used in VoiceEngine");
} // Register the ADM to the process thread, which will drive the error
// callback mechanism
if (_shared->process_thread() &&
_shared->process_thread()->RegisterModule(_shared->audio_device()) != 0)
{
_shared->SetLastError(VE_AUDIO_DEVICE_MODULE_ERROR, kTraceError,
"Init() failed to register the ADM");
return -1;
}
bool available(false); // --------------------
// Reinitialize the ADM // Register the AudioObserver implementation
if (_shared->audio_device()->RegisterEventObserver(this) != 0) {
_shared->SetLastError(VE_AUDIO_DEVICE_MODULE_ERROR, kTraceWarning,
"Init() failed to register event observer for the ADM");
} // Register the AudioTransport implementation
if (_shared->audio_device()->RegisterAudioCallback(this) != 0) {
_shared->SetLastError(VE_AUDIO_DEVICE_MODULE_ERROR, kTraceWarning,
"Init() failed to register audio callback for the ADM");
} // ADM initialization
if (_shared->audio_device()->Init() != 0)
{
_shared->SetLastError(VE_AUDIO_DEVICE_MODULE_ERROR, kTraceError,
"Init() failed to initialize the ADM");
return -1;
} // Initialize the default speaker
if (_shared->audio_device()->SetPlayoutDevice(
WEBRTC_VOICE_ENGINE_DEFAULT_DEVICE) != 0)
{
_shared->SetLastError(VE_AUDIO_DEVICE_MODULE_ERROR, kTraceInfo,
"Init() failed to set the default output device");
}
if (_shared->audio_device()->SpeakerIsAvailable(&available) != 0)
{
_shared->SetLastError(VE_CANNOT_ACCESS_SPEAKER_VOL, kTraceInfo,
"Init() failed to check speaker availability, trying to "
"initialize speaker anyway");
}
else if (!available)
{
_shared->SetLastError(VE_CANNOT_ACCESS_SPEAKER_VOL, kTraceInfo,
"Init() speaker not available, trying to initialize speaker "
"anyway");
}
if (_shared->audio_device()->InitSpeaker() != 0)
{
_shared->SetLastError(VE_CANNOT_ACCESS_SPEAKER_VOL, kTraceInfo,
"Init() failed to initialize the speaker");
} // Initialize the default microphone
if (_shared->audio_device()->SetRecordingDevice(
WEBRTC_VOICE_ENGINE_DEFAULT_DEVICE) != 0)
{
_shared->SetLastError(VE_SOUNDCARD_ERROR, kTraceInfo,
"Init() failed to set the default input device");
}
if (_shared->audio_device()->MicrophoneIsAvailable(&available) != 0)
{
_shared->SetLastError(VE_CANNOT_ACCESS_MIC_VOL, kTraceInfo,
"Init() failed to check microphone availability, trying to "
"initialize microphone anyway");
}
else if (!available)
{
_shared->SetLastError(VE_CANNOT_ACCESS_MIC_VOL, kTraceInfo,
"Init() microphone not available, trying to initialize "
"microphone anyway");
}
if (_shared->audio_device()->InitMicrophone() != 0)
{
_shared->SetLastError(VE_CANNOT_ACCESS_MIC_VOL, kTraceInfo,
"Init() failed to initialize the microphone");
} // Set number of channels
if (_shared->audio_device()->StereoPlayoutIsAvailable(&available) != 0) {
_shared->SetLastError(VE_SOUNDCARD_ERROR, kTraceWarning,
"Init() failed to query stereo playout mode");
}
if (_shared->audio_device()->SetStereoPlayout(available) != 0)
{
_shared->SetLastError(VE_SOUNDCARD_ERROR, kTraceWarning,
"Init() failed to set mono/stereo playout mode");
} // TODO(andrew): These functions don't tell us whether stereo recording
// is truly available. We simply set the AudioProcessing input to stereo
// here, because we have to wait until receiving the first frame to
// determine the actual number of channels anyway.
//
// These functions may be changed; tracked here:
// http://code.google.com/p/webrtc/issues/detail?id=204
_shared->audio_device()->StereoRecordingIsAvailable(&available);
if (_shared->audio_device()->SetStereoRecording(available) != 0)
{
_shared->SetLastError(VE_SOUNDCARD_ERROR, kTraceWarning,
"Init() failed to set mono/stereo recording mode");
} if (!audioproc) {
audioproc = AudioProcessing::Create(VoEId(_shared->instance_id(), -1));
if (!audioproc) {
LOG(LS_ERROR) << "Failed to create AudioProcessing.";
_shared->SetLastError(VE_NO_MEMORY);
return -1;
}
}
_shared->set_audio_processing(audioproc);
/*DECWANG_4RECORD设置混音模块调用指针
*/
_shared->outputall_mixer()->SetAudioProcessingModule(_shared->audio_processing());
_shared->outputall_mixer()->SetMixabilityStatus(_afmTransmitMixer, true);
_shared->outputall_mixer()->SetMixabilityStatus(_afmOutputMixer, true);
/**/ // Set the error state for any failures in this block.
_shared->SetLastError(VE_APM_ERROR);
if (audioproc->echo_cancellation()->set_device_sample_rate_hz(48000)) {
LOG_FERR1(LS_ERROR, set_device_sample_rate_hz, 48000);
return -1;
}
//DECWANG_ADD 20110620 FOR RECORD SYNC , // Assume 16 kHz mono until the audio frames are received from the capture
// device, at which point this can be updated.
if (audioproc->set_sample_rate_hz(16000)) {
LOG_FERR1(LS_ERROR, set_sample_rate_hz, 16000);
return -1;
}
if (audioproc->set_num_channels(1, 1) != 0) {
LOG_FERR2(LS_ERROR, set_num_channels, 1, 1);
return -1;
}
if (audioproc->set_num_reverse_channels(1) != 0) {
LOG_FERR1(LS_ERROR, set_num_reverse_channels, 1);
return -1;
} // Configure AudioProcessing components. All are disabled by default.
if (audioproc->high_pass_filter()->Enable(true) != 0) {
LOG_FERR1(LS_ERROR, high_pass_filter()->Enable, true);
return -1;
}
if (audioproc->echo_cancellation()->enable_drift_compensation(false) != 0) {
LOG_FERR1(LS_ERROR, enable_drift_compensation, false);
return -1;
}
if (audioproc->noise_suppression()->set_level(kDefaultNsMode) != 0) {
LOG_FERR1(LS_ERROR, noise_suppression()->set_level, kDefaultNsMode);
return -1;
}
GainControl* agc = audioproc->gain_control();
if (agc->set_analog_level_limits(kMinVolumeLevel, kMaxVolumeLevel) != 0) {
LOG_FERR2(LS_ERROR, agc->set_analog_level_limits, kMinVolumeLevel,
kMaxVolumeLevel);
return -1;
}
if (agc->set_mode(kDefaultAgcMode) != 0) {
LOG_FERR1(LS_ERROR, agc->set_mode, kDefaultAgcMode);
return -1;
}
if (agc->Enable(kDefaultAgcState) != 0) {
LOG_FERR1(LS_ERROR, agc->Enable, kDefaultAgcState);
return -1;
}
_shared->SetLastError(0); // Clear error state. #ifdef WEBRTC_VOICE_ENGINE_AGC
bool agc_enabled = agc->mode() == GainControl::kAdaptiveAnalog &&
agc->is_enabled();
if (_shared->audio_device()->SetAGC(agc_enabled) != 0) {
LOG_FERR1(LS_ERROR, audio_device()->SetAGC, agc_enabled);
_shared->SetLastError(VE_AUDIO_DEVICE_MODULE_ERROR);
// TODO(ajm): No error return here due to
// https://code.google.com/p/webrtc/issues/detail?id=1464
}
#endif return _shared->statistics().SetInitialized();
}

//////////////.h///////////////
//////////////.h///////////////
至此,结束,进行通话时就可以进行录音了.下一篇将介绍录制的文件如何提高语音质量和格式转换的问题了.See you Next.
转载请注明出处 decwang@qq.com

WebRTC录音(1)-实现通话双向录音的更多相关文章

  1. 手把手教你Android来去电通话自动录音的方法

    我们在使用Android手机打电话时,有时可能会需要对来去电通话自动录音,本文就详细讲解实现Android来去电通话自动录音的方法,大家按照文中的方法编写程序就可以完成此功能. 来去电自动录音的关键在 ...

  2. iOS - 处理通话或录音状态中界面错乱的重布局

    iphone设备在通话或录音状态中,状态栏下移了20个像素,这时controller的view的frame的高度就减少了20个像素,所以我们需要根据view的frame去布局子view.但是是,当状态 ...

  3. Android 6.0 双向通话自动录音

    package com.example.hgx.phoneinfo60.Recording; import android.content.BroadcastReceiver; import andr ...

  4. 微信小程序语音识别服务搭建全过程解析(https api开放,支持新接口mp3录音、老接口silk录音)

    silk v3(或新录音接口mp3)录音转olami语音识别和语义处理的api服务(ubuntu16.04服务器上实现) 重要的写在前面 重要事项一: 所有相关更新,我优先更新到我个人博客中,其它地方 ...

  5. @MarkFan 口语练习录音 20140415 [MDL演讲口语录音]

    Hi,everybody! 今天是2014年4月14日, 现在是晚上十一点零柒分. 一本励志的书,一场振奋人心的演讲,一次推心置腹的谈话, 最多只是在你背后小推你一下,最终决定是否迈出前进的步伐, 以 ...

  6. asterisk中使用dahdi通道呼出的注意事项

    asterisk中使用dahdi通道呼出的注意事项 在使用dahdi通道呼出的时候,可以在Dial中对呼出所使用的通道进行指定选择.以下面的例子来说明: 场景说明:数字板卡单E1,使用pri信令,1- ...

  7. 【Android】【录音】Android录音--AudioRecord、MediaRecorder

    [Android][录音]Android录音--AudioRecord.MediaRecorder Android提供了两个API用于实现录音功能:android.media.AudioRecord. ...

  8. Android录音--AudioRecord、MediaRecorder

    Android提供了两个API用于实现录音功能:android.media.AudioRecord.android.media.MediaRecorder. 网上有很多谈论这两个类的资料.现在大致总结 ...

  9. 【录音】Android录音--AudioRecord、MediaRecorder

    Android提供了两个API用于实现录音功能:android.media.AudioRecord.android.media.MediaRecorder. 网上有很多谈论这两个类的资料.现在大致总结 ...

随机推荐

  1. POJ 1850 Code 字符串 难度:1

    题意: 1 如果是严格升序的字母字符串,那么可以输出非0解码,否则不能译码输出0 2 字符串解码 遵循递增原则,其值为 到现在为止的所有按字母序小于该字符串的数量 + 1; #include < ...

  2. 向量和矩阵的范数及MATLAB调用函数

    范数就是长度的一种推广形式,数学语言叫一种度量.比如有一个平面向量,有两个分量来描述:横坐标和纵坐标.向量的二范数就是欧几里得意义下的这个向量的长度.还有一些诸如极大值范数,就是横坐标或者纵坐标的最大 ...

  3. [vijos P1626] 爱在心中

    做完Victoria的舞会3,挑了vijos里强连通分量里面难度值最低的题目,也就是这道.先把第一小问做了,纯Tarjan,只是我学的时候的标程是用邻接表的,这题数据小于是用了邻接矩阵,两者之间的切换 ...

  4. matlab blkproc

    有关blkproc 命令的使用 (2011-07-31 09:52:57) 标签: 杂谈 分类: matlab使用 如果你让matlab帮你计算最好的块大小,用bestblk函数,[MB,NB] = ...

  5. Oracle GoldenGate 12c (12.1.2.0.1) for IBM DB2 iSeries

    OGG 12.1.2.0.1 for iSeries 在2014.2.15发布,主要新增如下功能: 本地交付(Native Delivery Replicat):新功能允许用户在IBM i服务器上安装 ...

  6. 找第k大的数

    (找第k大的数) 给定一个长度为1,000,000的无序正整数序列,以及另一个数n(1<=n<=1000000),接下来以类似快速排序的方法找到序列中第n大的数(关于第n大的数:例如序列{ ...

  7. linux 杀死进程的方法

    # kill -pid 注释:标准的kill命令通常都能达到目的.终止有问题的进程,并把进程的资源释放给系统.然而,如果进程启动了子进程,只杀死父进程,子进程仍在运行,因此仍消耗资源.为了防止这些所谓 ...

  8. poj3667 线段树 区间合并

    //Accepted 3728 KB 1079 ms //线段树 区间合并 #include <cstdio> #include <cstring> #include < ...

  9. (转)javascript中event对象详解

    原文:http://jiajiale.iteye.com/blog/195906 javascript中event对象详解          博客分类: javaScript JavaScriptCS ...

  10. SQL2008:在与 SQL Server 建立连接时出现与网络相关的或特定于实例的错误。未找到或无法访问服务器。请验证实例名称是否正确并且 SQL Server 已配置为允许远程连接。

    错误: 解决方案: 1.查看服务是否开启,如果没有开启,请开启服务. 2.服务器名称栏:格式:主机名\实例名或者ip\实例名 如:10.10.4.81\SQLSERVER