spice/client/record_channel.cpp
Christophe Fergeau 78c1465ed3 add #include <config.h> to all source files
When using config.h, it must be the very first include in all source
files since it contains #define that may change the compilation process
(eg libc structure layout changes when it's used to enable large file
support on 32 bit x86 archs). This commit adds it at the beginning
of all .c and .cpp files
2011-05-03 14:44:10 +02:00

286 lines
8.3 KiB
C++

/*
Copyright (C) 2009 Red Hat, Inc.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library 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
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, see <http://www.gnu.org/licenses/>.
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include "common.h"
#include "red_client.h"
#include "audio_channels.h"
#include "audio_devices.h"
#define NUM_SAMPLES_MESSAGES 4
static uint32_t get_mm_time()
{
return uint32_t(Platform::get_monolithic_time() / (1000 * 1000));
}
class RecordSamplesMessage: public RedChannel::OutMessage {
public:
RecordSamplesMessage(RecordChannel& channel);
virtual ~RecordSamplesMessage();
virtual RedPeer::OutMessage& peer_message() { return *_message;}
virtual void release();
private:
RecordChannel& _channel;
RedPeer::OutMessage *_message;
};
RecordSamplesMessage::RecordSamplesMessage(RecordChannel& channel)
: _channel (channel)
, _message (new Message(SPICE_MSGC_RECORD_DATA))
{
}
RecordSamplesMessage::~RecordSamplesMessage()
{
delete _message;
}
void RecordSamplesMessage::release()
{
_channel.release_message(this);
}
int RecordChannel::data_mode = SPICE_AUDIO_DATA_MODE_CELT_0_5_1;
class RecordHandler: public MessageHandlerImp<RecordChannel, SPICE_CHANNEL_RECORD> {
public:
RecordHandler(RecordChannel& channel)
: MessageHandlerImp<RecordChannel, SPICE_CHANNEL_RECORD>(channel) {}
};
RecordChannel::RecordChannel(RedClient& client, uint32_t id)
: RedChannel(client, SPICE_CHANNEL_RECORD, id, new RecordHandler(*this))
, _wave_recorder (NULL)
, _mode (SPICE_AUDIO_DATA_MODE_INVALID)
, _celt_mode (NULL)
, _celt_encoder (NULL)
{
for (int i = 0; i < NUM_SAMPLES_MESSAGES; i++) {
_messages.push_front(new RecordSamplesMessage(*this));
}
RecordHandler* handler = static_cast<RecordHandler*>(get_message_handler());
handler->set_handler(SPICE_MSG_MIGRATE, &RecordChannel::handle_migrate);
handler->set_handler(SPICE_MSG_SET_ACK, &RecordChannel::handle_set_ack);
handler->set_handler(SPICE_MSG_PING, &RecordChannel::handle_ping);
handler->set_handler(SPICE_MSG_WAIT_FOR_CHANNELS, &RecordChannel::handle_wait_for_channels);
handler->set_handler(SPICE_MSG_DISCONNECTING, &RecordChannel::handle_disconnect);
handler->set_handler(SPICE_MSG_NOTIFY, &RecordChannel::handle_notify);
handler->set_handler(SPICE_MSG_RECORD_START, &RecordChannel::handle_start);
set_capability(SPICE_RECORD_CAP_CELT_0_5_1);
}
RecordChannel::~RecordChannel(void)
{
while (!_messages.empty()) {
RecordSamplesMessage *mes;
mes = *_messages.begin();
_messages.pop_front();
delete mes;
}
delete _wave_recorder;
if (_celt_encoder) {
celt051_encoder_destroy(_celt_encoder);
}
if (_celt_mode) {
celt051_mode_destroy(_celt_mode);
}
}
bool RecordChannel::abort(void)
{
return (!_wave_recorder || _wave_recorder->abort()) && RedChannel::abort();
}
void RecordChannel::on_connect()
{
Message* message = new Message(SPICE_MSGC_RECORD_MODE);
SpiceMsgcRecordMode mode;
mode.time = get_mm_time();
mode.mode = _mode =
test_capability(SPICE_RECORD_CAP_CELT_0_5_1) ? RecordChannel::data_mode :
SPICE_AUDIO_DATA_MODE_RAW;
_marshallers->msgc_record_mode(message->marshaller(), &mode);
post_message(message);
}
void RecordChannel::send_start_mark()
{
Message* message = new Message(SPICE_MSGC_RECORD_START_MARK);
SpiceMsgcRecordStartMark start_mark;
start_mark.time = get_mm_time();
_marshallers->msgc_record_start_mark(message->marshaller(), &start_mark);
post_message(message);
}
void RecordChannel::handle_start(RedPeer::InMessage* message)
{
RecordHandler* handler = static_cast<RecordHandler*>(get_message_handler());
SpiceMsgRecordStart* start = (SpiceMsgRecordStart*)message->data();
handler->set_handler(SPICE_MSG_RECORD_START, NULL);
handler->set_handler(SPICE_MSG_RECORD_STOP, &RecordChannel::handle_stop);
ASSERT(!_wave_recorder && !_celt_mode && !_celt_encoder);
// for now support only one setting
if (start->format != SPICE_AUDIO_FMT_S16) {
THROW("unexpected format");
}
int bits_per_sample = 16;
try {
_wave_recorder = Platform::create_recorder(*this, start->frequency,
bits_per_sample,
start->channels);
} catch (...) {
LOG_WARN("create recorder failed");
return;
}
int frame_size = 256;
int celt_mode_err;
_frame_bytes = frame_size * bits_per_sample * start->channels / 8;
if (!(_celt_mode = celt051_mode_create(start->frequency, start->channels, frame_size,
&celt_mode_err))) {
THROW("create celt mode failed %d", celt_mode_err);
}
if (!(_celt_encoder = celt051_encoder_create(_celt_mode))) {
THROW("create celt encoder failed");
}
send_start_mark();
_wave_recorder->start();
}
void RecordChannel::handle_stop(RedPeer::InMessage* message)
{
RecordHandler* handler = static_cast<RecordHandler*>(get_message_handler());
handler->set_handler(SPICE_MSG_RECORD_START, &RecordChannel::handle_start);
handler->set_handler(SPICE_MSG_RECORD_STOP, NULL);
if (!_wave_recorder) {
return;
}
ASSERT(_celt_mode && _celt_encoder);
_wave_recorder->stop();
celt051_encoder_destroy(_celt_encoder);
_celt_encoder = NULL;
celt051_mode_destroy(_celt_mode);
_celt_mode = NULL;
delete _wave_recorder;
_wave_recorder = NULL;
}
RecordSamplesMessage* RecordChannel::get_message()
{
Lock lock(_messages_lock);
if (_messages.empty()) {
return NULL;
}
RecordSamplesMessage* ret = *_messages.begin();
_messages.pop_front();
return ret;
}
void RecordChannel::release_message(RecordSamplesMessage *message)
{
Lock lock(_messages_lock);
_messages.push_front(message);
}
void RecordChannel::add_event_source(EventSources::File& event_source)
{
get_process_loop().add_file(event_source);
}
void RecordChannel::remove_event_source(EventSources::File& event_source)
{
get_process_loop().remove_file(event_source);
}
void RecordChannel::add_event_source(EventSources::Trigger& event_source)
{
get_process_loop().add_trigger(event_source);
}
void RecordChannel::remove_event_source(EventSources::Trigger& event_source)
{
get_process_loop().remove_trigger(event_source);
}
#define FRAME_SIZE 256
#define CELT_BIT_RATE (64 * 1024)
#define CELT_COMPRESSED_FRAME_BYTES (FRAME_SIZE * CELT_BIT_RATE / 44100 / 8)
void RecordChannel::push_frame(uint8_t *frame)
{
RecordSamplesMessage *message;
ASSERT(_frame_bytes == FRAME_SIZE * 4);
if (!(message = get_message())) {
DBG(0, "blocked");
return;
}
uint8_t celt_buf[CELT_COMPRESSED_FRAME_BYTES];
int n;
if (_mode == SPICE_AUDIO_DATA_MODE_CELT_0_5_1) {
n = celt051_encode(_celt_encoder, (celt_int16_t *)frame, NULL, celt_buf,
CELT_COMPRESSED_FRAME_BYTES);
if (n < 0) {
THROW("celt encode failed");
}
frame = celt_buf;
} else {
n = _frame_bytes;
}
RedPeer::OutMessage& peer_message = message->peer_message();
peer_message.reset(SPICE_MSGC_RECORD_DATA);
SpiceMsgcRecordPacket packet;
packet.time = get_mm_time();
_marshallers->msgc_record_data(peer_message.marshaller(), &packet);
spice_marshaller_add(peer_message.marshaller(), frame, n);
post_message(message);
}
class RecordFactory: public ChannelFactory {
public:
RecordFactory() : ChannelFactory(SPICE_CHANNEL_RECORD) {}
virtual RedChannel* construct(RedClient& client, uint32_t id)
{
return new RecordChannel(client, id);
}
};
static RecordFactory factory;
ChannelFactory& RecordChannel::Factory()
{
return factory;
}