libjingleでportaudioを使ったmediaengineをつくった
Posted on 10月 18, 2008
Filed Under portaudio, libjingle |
置いておこう。
portaudioが、マイクからデータが取れたとき、スピーカーに音を流せるとき、にcallbackを呼んでくれる形なので、
・マイクから音をとってくるところ
・スピーカーに音を流すところ
にバッファを持っている。
覚えたてのC++のSTLの明示的cast、queueを使っています。
C++おもろいなー
libjingleが確保してくれたP2Pセッションに音声データを流すときに、
CODE:
-
network_interface_->SendPacket(const void*, size_t);
ってのを呼ぶんだけど、
queue
音声は生のwavを送っているので、遅延は
DirectAudioを使ってたらそこで50msくらい(らしい?)
+バッファ分の遅延、
+portaudioの遅延
+通信の遅延
くらいなのかな。なんか体感かなり遅延してるのは後で調べる。
portaudiomediaengine.cc
C++:
-
#include "talk/third_party/mediastreamer/mediastream.h"
-
#include <fcntl .h>
-
#include <iostream>
-
#include "talk/base/logging.h"
-
#include "talk/base/thread.h"
-
#include "talk/session/phone/codec.h"
-
#include "talk/session/phone/portaudiomediaengine.h"
-
-
#include <sstream>
-
#include <iostream>
-
#include <iomanip>
-
#include <string>
-
#include <queue>
-
#include "portaudio.h"
-
#include <time .h>
-
-
using namespace cricket;
-
-
-
static int playbackCallback( const void *inputBuffer,
-
void *outputBuffer,
-
unsigned long framesPerBuffer,
-
const PaStreamCallbackTimeInfo* timeInfo,
-
PaStreamCallbackFlags statusFlags,
-
void *userData
-
)
-
{
-
PortAudioMediaChannel* channel = static_cast<portaudiomediachannel *>(userData);
-
if ( inputBuffer!=0 ) {
-
channel->saveFromMicrophone( static_cast<const float*>(inputBuffer), framesPerBuffer );
-
}
-
channel->pushToSpeaker( static_cast<float *>(outputBuffer), framesPerBuffer );
-
channel->SignalReadFromMicEvent( channel );
-
return paContinue;
-
}
-
-
-
PortAudioMediaChannel::PortAudioMediaChannel(PortAudioMediaEngine* eng) :
-
pt_(-1),
-
audio_stream_(0),
-
engine_(eng)
-
{
-
PaStreamParameters inputParameters;
-
inputParameters.device = Pa_GetDefaultInputDevice(); /* default input device */
-
if (inputParameters.device == paNoDevice) {
-
fprintf(stderr,"Error: No default input device.\n");
-
//goto done;
-
}
-
inputParameters.channelCount = 2; /* stereo input */
-
inputParameters.sampleFormat = PA_SAMPLE_TYPE;
-
inputParameters.suggestedLatency = Pa_GetDeviceInfo( inputParameters.device )->defaultLowInputLatency;
-
inputParameters.hostApiSpecificStreamInfo = 0;
-
-
PaStreamParameters outputParameters;
-
outputParameters.device = Pa_GetDefaultOutputDevice(); /* default output device */
-
if (outputParameters.device == paNoDevice) {
-
fprintf(stderr,"Error: No default output device.\n");
-
//goto done;
-
}
-
outputParameters.channelCount = 2; /* stereo output */
-
outputParameters.sampleFormat = PA_SAMPLE_TYPE;
-
outputParameters.suggestedLatency = Pa_GetDeviceInfo( outputParameters.device )->defaultLowOutputLatency;
-
outputParameters.hostApiSpecificStreamInfo = 0;
-
-
SignalReadFromMicEvent.connect(this, &PortAudioMediaChannel::OnReadFromMic);
-
-
// Initialize PortAudio
-
int err = Pa_OpenStream(
-
&stream_,
-
&inputParameters,
-
&outputParameters,
-
SAMPLE_RATE, // 44100
-
paFramesPerBufferUnspecified, // FRAMES_PER_BUFFER
-
paClipOff,
-
playbackCallback,
-
this
-
);
-
if (err != paNoError)
-
fprintf(stderr, "Error creating a PortAudio stream: %s\n", Pa_GetErrorText(err));
-
-
}
-
-
PortAudioMediaChannel::~PortAudioMediaChannel() {
-
if (stream_) {
-
Pa_CloseStream(stream_);
-
}
-
}
-
-
void PortAudioMediaChannel::SetCodecs(const std::vector<codec> &codecs) {
-
bool first = true;
-
std::vector</codec><codec>::const_iterator i;
-
-
for (i = codecs.begin(); i <codecs.end(); i++) {
-
if (!engine_->FindCodec(*i))
-
continue;
-
if (first) {
-
LOG(LS_INFO) <<"Using " <<i->name <<"/" <<i->clockrate;
-
pt_ = i->id;
-
first = false;
-
}
-
}
-
-
if (first) {
-
// We're being asked to set an empty list of codecs. This will only happen when
-
// working with a buggy client; let's try PCMU.
-
LOG(LS_WARNING) <<"Received empty list of codces; using PCMU/8000";
-
}
-
}
-
-
// マイクから新しいデータがとれたら、リモートに送る
-
void PortAudioMediaChannel::OnReadFromMic( PortAudioMediaChannel* channel )
-
{
-
//char *buf[max_size];
-
int size = PORTAUDIO_PACKET_LENGTH;
-
float buff[PORTAUDIO_PACKET_LENGTH/4];
-
int len;
-
talk_base::CritScope cs(&crit_microphone);
-
-
int i=0;
-
for ( i=0; i<size/sizeof(float) && !qu_microphone.empty(); i++ ) {
-
buff[i] = qu_microphone.front();
-
qu_microphone.pop();
-
}
-
if ( network_interface_ && !mute_ ) {
-
const void *buf = static_cast<const void*>(buff);
-
network_interface_->SendPacket( buff, i * sizeof(float) );
-
}
-
char dateStr [9];
-
_strtime( dateStr );
-
std::cout <<"[" <<dateStr <<"][OnReadFromMic]qu_microphone.size(): " <<qu_microphone.size() <<std::endl;
-
}
-
-
// 届いたパケットをスピーカーのバッファに送る
-
void PortAudioMediaChannel::OnPacketReceived( const void *data, int len ) {
-
const float* reader = static_cast<const float*>(data);
-
talk_base::CritScope cs(&crit_speaker);
-
-
LOG(INFO) <<"[PMC]OnPacketReceived data: " <<std::setprecision(3) <<reader[0] <<" len: " <<len <<"qu_speaker.size(): " <<qu_speaker.size();
-
-
for ( int i=0; i<len/4; i++ ) {
-
qu_speaker.push( *reader );
-
reader++;
-
}
-
}
-
-
void PortAudioMediaChannel::SetPlayout(bool playout) {
-
-
if (!stream_)
-
return;
-
-
if (play_ && !playout) {
-
if ( Pa_IsStreamActive(stream_) ) {
-
int err = Pa_StopStream(stream_);
-
if (err != paNoError) {
-
fprintf(stderr, "Error stopping PortAudio stream: %s\n", Pa_GetErrorText(err));
-
LOG(LS_INFO) <<"Error stopping PortAudio stream: %s\n", Pa_GetErrorText(err);
-
return;
-
}
-
}
-
play_ = false;
-
}
-
else if (!play_ && playout) {
-
if ( !Pa_IsStreamActive(stream_) ) {
-
int err = Pa_StartStream(stream_);
-
if (err != paNoError) {
-
fprintf(stderr, "Error starting PortAudio stream: %s\n", Pa_GetErrorText(err));
-
LOG(LS_INFO) <<"Error starting PortAudio stream: %s\n", Pa_GetErrorText(err);
-
return;
-
}
-
}
-
play_ = true;
-
}
-
-
}
-
-
void PortAudioMediaChannel::SetSend(bool send) {
-
mute_ = !send;
-
}
-
-
int PortAudioMediaChannel::GetOutputLevel() {
-
return 1;
-
}
-
-
// bufにlen分だけ書き込む => 音が出る
-
// リモートから届いたのを書き込む
-
void PortAudioMediaChannel::pushToSpeaker( float* buf, int len ) {
-
talk_base::CritScope cs(&crit_speaker);
-
int i=0;
-
-
// 遅延が大きかったら古いの消す
-
while ( qu_speaker.size()> (MAX_SPEAKER_QUEUE_SIZE) ) {
-
qu_speaker.pop();
-
}
-
-
for( i=0; i <len && qu_speaker.size()> 0; i++ ) {
-
*buf++ = qu_speaker.front();
-
qu_speaker.pop();
-
if( NUM_CHANNELS == 2 ){
-
*buf++ = qu_speaker.front();
-
qu_speaker.pop();
-
}
-
}
-
bool is_empty = false;
-
for( i=i; i <len; i++ ) {
-
is_empty = true;
-
*buf++ = SAMPLE_SILENCE;
-
if( NUM_CHANNELS == 2 ){
-
*buf++ = SAMPLE_SILENCE;
-
}
-
}
-
char dateStr [9];
-
_strtime( dateStr );
-
if ( is_empty ) {
-
std::cout <<"[" <<dateStr <<"][pushToSpeaker]empty!!" <<std::endl;
-
}
-
std::cout <<"[" <<dateStr <<"][pushToSpeaker]qu_speaker.size(): " <<qu_speaker.size() <<std::endl;
-
}
-
-
// マイクから来たのをためとく
-
void PortAudioMediaChannel::saveFromMicrophone( const float* buf, int len ) {
-
talk_base::CritScope cs(&crit_microphone);
-
-
for( int i=0; i<len; i++ ) {
-
qu_microphone.push( *buf++ );
-
if( NUM_CHANNELS == 2 ){
-
qu_microphone.push( *buf++ );
-
}
-
}
-
char dateStr [9];
-
_strtime( dateStr );
-
std::cout <<"[" <<dateStr <<"][saveFromMicrophone]qu_microphone.size(): " <<qu_microphone.size() <<std::endl;
-
}
portaudiomediaengine.h
C++:
-
/*
-
* Jingle call example
-
* Copyright 2004--2005, Google Inc.
-
*
-
* This program is free software; you can redistribute it and/or modify
-
* it under the terms of the GNU General Public License as published by
-
* the Free Software Foundation; either version 2 of the License, or
-
* (at your option) any later version.
-
*
-
* This program is distributed in the hope that it will be useful,
-
* but WITHOUT ANY WARRANTY; without even the implied warranty of
-
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-
* GNU General Public License for more details.
-
*
-
* You should have received a copy of the GNU General Public License
-
* along with this program; if not, write to the Free Software
-
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
-
*/
-
-
// PortAudioMediaEngine is a Linphone implementation of MediaEngine
-
-
#ifndef TALK_SESSION_PHONE_PORTAUDIOMEDIAENGINE_H_
-
#define TALK_SESSION_PHONE_PORTAUDIOMEDIAENGINE_H_
-
-
#include "talk/third_party/mediastreamer/mediastream.h"
-
#include "talk/base/asyncsocket.h"
-
#include "talk/base/scoped_ptr.h"
-
#include "talk/session/phone/mediaengine.h"
-
#include "talk/base/criticalsection.h"
-
#include "portaudio.h"
-
#include <queue>
-
#include <cmath>
-
-
// Engine settings
-
#define ENGINE_BUFFER_SIZE 2048
-
-
// PortAudio settings
-
#define PA_SAMPLE_TYPE (paFloat32) // 32 bit floating point output
-
typedef float SAMPLE;
-
#define SAMPLE_RATE (44100)
-
//#define SAMPLE_RATE (11025)
-
//#define FRAMES_PER_BUFFER (1024)
-
#define FRAMES_PER_BUFFER (256)
-
#define SAMPLE_SILENCE (0.0f)
-
#define NUM_CHANNELS (2)
-
//#define NUM_CHANNELS (1)
-
-
//#define PORTAUDIO_PACKET_LENGTH (2048)
-
#define PORTAUDIO_PACKET_LENGTH (40960) // 一度に送る音声パケットのサイズ,これなら聞ける
-
#define MAX_ALLOWED_LATENCY (0.5) // 許容する音声の最大遅延[s]
-
#define MAX_SPEAKER_QUEUE_SIZE (MAX_ALLOWED_LATENCY * SAMPLE_RATE * NUM_CHANNELS) // キューの最大サイズ。これを超えたら先頭のは破棄する
-
-
#ifndef M_PI
-
#define M_PI (3.14159265)
-
#endif
-
-
-
-
-
namespace cricket {
-
-
-
-
-
#define TABLE_SIZE (200)
-
typedef struct
-
{
-
float sine[TABLE_SIZE];
-
int left_phase;
-
int right_phase;
-
char message[20];
-
}
-
paTestData;
-
-
-
typedef struct {
-
std::queue<sample> qu;
-
}
-
paTestData2;
-
-
-
class PortAudioMediaEngine;
-
-
class PortAudioMediaChannel : public MediaChannel {
-
public:
-
PortAudioMediaChannel(PortAudioMediaEngine *eng);
-
virtual ~PortAudioMediaChannel();
-
-
virtual void SetCodecs(const std::vector<codec> &codecs);
-
virtual void OnPacketReceived(const void *data, int len);
-
-
virtual void SetPlayout(bool playout);
-
virtual void SetSend(bool send);
-
-
virtual int GetOutputLevel();
-
bool mute() {return mute_;}
-
-
virtual void StartMediaMonitor(VoiceChannel * voice_channel, uint32 cms) {}
-
virtual void StopMediaMonitor() {}
-
-
// portaudio
-
void pushToSpeaker( float*, int );
-
void saveFromMicrophone( const float*, int );
-
sigslot::signal1<portaudiomediachannel *> SignalReadFromMicEvent; // ready to read
-
-
-
paTestData data;
-
std::queue<sample> qu_sine;
-
-
-
paTestData2 data2;
-
int totalFrames;
-
-
-
protected:
-
// portaudio
-
void readBuffer(float*, float**, float*, float*, float*, int);
-
void writeBuffer(float*, float*, float**, float*, float*, int);
-
-
private:
-
PortAudioMediaEngine* engine_;
-
AudioStream* audio_stream_;
-
talk_base::scoped_ptr<talk_base ::AsyncSocket> socket_;
-
void OnReadFromMic(PortAudioMediaChannel*);
-
-
int pt_;
-
bool mute_;
-
bool play_;
-
-
talk_base::CriticalSection crit_speaker;
-
talk_base::CriticalSection crit_microphone;
-
-
// portaudio
-
PaStream* stream_;
-
std::queue<sample> qu_microphone; // ローカルのマイク用
-
std::queue</sample><sample> qu_speaker; // リモートから届いたのをためておく
-
-
};
-
-
class PortAudioMediaEngine : public MediaEngine {
-
public:
-
PortAudioMediaEngine();
-
~PortAudioMediaEngine();
-
virtual bool Init();
-
virtual void Terminate();
-
-
virtual MediaChannel *CreateChannel();
-
-
virtual int SetAudioOptions(int options);
-
virtual int SetSoundDevices(int wave_in_device, int wave_out_device);
-
-
virtual float GetCurrentQuality();
-
virtual int GetInputLevel();
-
-
virtual std::vector<codec , std::allocator<Codec>> codecs() {return codecs_;}
-
virtual bool FindCodec(const Codec&);
-
-
private:
-
std::vector</codec><codec , std::allocator<Codec>> codecs_;
-
};
-
-
} // namespace cricket
-
-
#endif // TALK_SESSION_PHONE_PORTAUDIOMEDIAENGINE_H_
やりたいのは電話でなく一方向のストリーミングなので、
そろそろ自前のxmppサーバをたてて、
libjingleのxmppclientをいじることになるのかな。
あとspeexコーデックをいれてみたい。
Comments
Leave a Reply