diff options
author | Yaniv Kamay <ykamay@redhat.com> | 2009-09-19 21:25:46 +0300 |
---|---|---|
committer | Yaniv Kamay <ykamay@redhat.com> | 2009-10-14 15:06:41 +0200 |
commit | c1b79eb035fa158fb2ac3bc8e559809611070016 (patch) | |
tree | 3348dd749a700dedf87c9b16fe8be77c62928df8 /client/playback_channel.cpp | |
download | spice-c1b79eb035fa158fb2ac3bc8e559809611070016.tar.gz spice-c1b79eb035fa158fb2ac3bc8e559809611070016.tar.xz spice-c1b79eb035fa158fb2ac3bc8e559809611070016.zip |
fresh start
Diffstat (limited to 'client/playback_channel.cpp')
-rw-r--r-- | client/playback_channel.cpp | 355 |
1 files changed, 355 insertions, 0 deletions
diff --git a/client/playback_channel.cpp b/client/playback_channel.cpp new file mode 100644 index 00000000..fd20b0ac --- /dev/null +++ b/client/playback_channel.cpp @@ -0,0 +1,355 @@ +/* + Copyright (C) 2009 Red Hat, 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, see <http://www.gnu.org/licenses/>. +*/ + +#include "common.h" +#include "red_client.h" +#include "audio_channels.h" +#include "audio_devices.h" + +//#define WAVE_CAPTURE +#ifdef WAVE_CAPTURE + +#include <fcntl.h> + +#define WAVE_BUF_SIZE (1024 * 1024 * 20) + +typedef struct __attribute__ ((__packed__)) ChunkHeader { + uint32_t id; + uint32_t size; +} ChunkHeader; + +typedef struct __attribute__ ((__packed__)) FormatInfo { + uint16_t compression_code; + uint16_t num_channels; + uint32_t sample_rate; + uint32_t average_bytes_per_second; + uint16_t block_align; + uint16_t bits_per_sample; + //uint16_t extra_format_bytes; + //uint8_t extra[0]; +} FormatInfo; + +static uint8_t* wave_buf = NULL; +static uint8_t* wave_now = NULL; +static uint8_t* wave_end = NULL; +static bool wave_blocked = false; + +static void write_all(int fd, uint8_t* data, uint32_t size) +{ + while (size) { + int n = write(fd, data, size); + if (n == -1) { + if (errno != EINTR) { + throw Exception(fmt("%s: failed") % __FUNCTION__); + } + } else { + data += n; + size -= n; + } + } +} + +static void write_wave() +{ + static uint32_t file_id = 0; + char file_name[100]; + ChunkHeader header; + FormatInfo format; + + if (wave_buf == wave_now) { + return; + } + + sprintf(file_name, "/tmp/%u.wav", ++file_id); + int fd = open(file_name, O_CREAT | O_TRUNC | O_WRONLY, 0644); + if (fd == -1) { + DBG(0, fmt("open file %s failed") % file_name); + return; + } + + memcpy((char *)&header.id, "RIFF", 4); + header.size = 4; + write_all(fd, (uint8_t *)&header, sizeof(header)); + write_all(fd, (uint8_t *)"WAVE", 4); + + memcpy((char *)&header.id, "fmt ", 4); + header.size = sizeof(format); + write_all(fd, (uint8_t *)&header, sizeof(header)); + + format.compression_code = 1; + format.num_channels = 2; + format.sample_rate = 44100; + format.average_bytes_per_second = format.sample_rate * 4; + format.block_align = 4; + format.bits_per_sample = 16; + write_all(fd, (uint8_t *)&format, sizeof(format)); + + memcpy((char *)&header.id, "data", 4); + header.size = wave_now - wave_buf; + write_all(fd, (uint8_t *)&header, sizeof(header)); + write_all(fd, wave_buf, header.size); + close(fd); +} + +static void init_wave() +{ + if (!wave_buf) { + wave_buf = new uint8_t[WAVE_BUF_SIZE]; + } + wave_now = wave_buf; + wave_end = wave_buf + WAVE_BUF_SIZE; +} + +static void start_wave() +{ + wave_blocked = false; + wave_now = wave_buf; +} + +static void put_wave_data(uint8_t *data, uint32_t size) +{ + if (wave_blocked || size > wave_end - wave_now) { + wave_blocked = true; + return; + } + memcpy((void *)wave_now, (void *)data, size); + wave_now += size; +} + +static void end_wave() +{ + write_wave(); +} + +#endif + +class PlaybackHandler: public MessageHandlerImp<PlaybackChannel, RED_PLAYBACK_MESSAGES_END> { +public: + PlaybackHandler(PlaybackChannel& channel) + : MessageHandlerImp<PlaybackChannel, RED_PLAYBACK_MESSAGES_END>(channel) {} +}; + +PlaybackChannel::PlaybackChannel(RedClient& client, uint32_t id) + : RedChannel(client, RED_CHANNEL_PLAYBACK, id, new PlaybackHandler(*this), + Platform::PRIORITY_HIGH) + , _wave_player (NULL) + , _mode (RED_AUDIO_DATA_MODE_INVALD) + , _celt_mode (NULL) + , _celt_decoder (NULL) + , _playing (false) +{ +#ifdef WAVE_CAPTURE + init_wave(); +#endif + PlaybackHandler* handler = static_cast<PlaybackHandler*>(get_message_handler()); + + handler->set_handler(RED_MIGRATE, &PlaybackChannel::handle_migrate, 0); + handler->set_handler(RED_SET_ACK, &PlaybackChannel::handle_set_ack, sizeof(RedSetAck)); + handler->set_handler(RED_PING, &PlaybackChannel::handle_ping, sizeof(RedPing)); + handler->set_handler(RED_WAIT_FOR_CHANNELS, &PlaybackChannel::handle_wait_for_channels, + sizeof(RedWaitForChannels)); + handler->set_handler(RED_DISCONNECTING, &PlaybackChannel::handle_disconnect, + sizeof(RedDisconnect)); + handler->set_handler(RED_NOTIFY, &PlaybackChannel::handle_notify, sizeof(RedNotify)); + + handler->set_handler(RED_PLAYBACK_MODE, &PlaybackChannel::handle_mode, + sizeof(RedPlaybackMode)); + + set_capability(RED_PLAYBACK_CAP_CELT_0_5_1); +} + +PlaybackChannel::~PlaybackChannel(void) +{ + delete _wave_player; + + if (_celt_decoder) { + celt051_decoder_destroy(_celt_decoder); + } + + if (_celt_mode) { + celt051_mode_destroy(_celt_mode); + } +} + +bool PlaybackChannel::abort(void) +{ + return (!_wave_player || _wave_player->abort()) && RedChannel::abort(); +} + +void PlaybackChannel::set_data_handler() +{ + PlaybackHandler* handler = static_cast<PlaybackHandler*>(get_message_handler()); + + if (_mode == RED_AUDIO_DATA_MODE_RAW) { + handler->set_handler(RED_PLAYBACK_DATA, &PlaybackChannel::handle_raw_data, 0); + } else if (_mode == RED_AUDIO_DATA_MODE_CELT_0_5_1) { + handler->set_handler(RED_PLAYBACK_DATA, &PlaybackChannel::handle_celt_data, 0); + } else { + THROW("invalid mode"); + } +} + +void PlaybackChannel::handle_mode(RedPeer::InMessage* message) +{ + RedPlaybackMode* playbacke_mode = (RedPlaybackMode*)message->data(); + if (playbacke_mode->mode != RED_AUDIO_DATA_MODE_RAW && + playbacke_mode->mode != RED_AUDIO_DATA_MODE_CELT_0_5_1) { + THROW("invalid mode"); + } + + _mode = playbacke_mode->mode; + if (_playing) { + set_data_handler(); + return; + } + + PlaybackHandler* handler = static_cast<PlaybackHandler*>(get_message_handler()); + handler->set_handler(RED_PLAYBACK_START, &PlaybackChannel::handle_start, + sizeof(RedPlaybackStart)); +} + +void PlaybackChannel::null_handler(RedPeer::InMessage* message) +{ +} + +void PlaybackChannel::disable() +{ + PlaybackHandler* handler = static_cast<PlaybackHandler*>(get_message_handler()); + + handler->set_handler(RED_PLAYBACK_START, &PlaybackChannel::null_handler, 0); + handler->set_handler(RED_PLAYBACK_STOP, &PlaybackChannel::null_handler, 0); + handler->set_handler(RED_PLAYBACK_MODE, &PlaybackChannel::null_handler, 0); + handler->set_handler(RED_PLAYBACK_DATA, &PlaybackChannel::null_handler, 0); +} + +void PlaybackChannel::handle_start(RedPeer::InMessage* message) +{ + PlaybackHandler* handler = static_cast<PlaybackHandler*>(get_message_handler()); + RedPlaybackStart* start = (RedPlaybackStart*)message->data(); + + handler->set_handler(RED_PLAYBACK_START, NULL, 0); + handler->set_handler(RED_PLAYBACK_STOP, &PlaybackChannel::handle_stop, 0); + +#ifdef WAVE_CAPTURE + start_wave(); +#endif + if (!_wave_player) { + // for now support only one setting + int celt_mode_err; + + if (start->format != RED_AUDIO_FMT_S16) { + THROW("unexpected format"); + } + int bits_per_sample = 16; + int frame_size = 256; + _frame_bytes = frame_size * start->channels * bits_per_sample / 8; + try { + _wave_player = Platform::create_player(start->frequency, bits_per_sample, + start->channels); + } catch (...) { + LOG_WARN("create player failed"); + //todo: support disconnecting singel channel + disable(); + return; + } + + 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_decoder = celt051_decoder_create(_celt_mode))) { + THROW("create celt decoder"); + } + } + _playing = true; + _frame_count = 0; + set_data_handler(); +} + +void PlaybackChannel::handle_stop(RedPeer::InMessage* message) +{ + PlaybackHandler* handler = static_cast<PlaybackHandler*>(get_message_handler()); + + handler->set_handler(RED_PLAYBACK_STOP, NULL, 0); + handler->set_handler(RED_PLAYBACK_DATA, NULL, 0); + handler->set_handler(RED_PLAYBACK_START, &PlaybackChannel::handle_start, + sizeof(RedPlaybackStart)); + +#ifdef WAVE_CAPTURE + end_wave(); +#endif + _wave_player->stop(); + _playing = false; +} + +void PlaybackChannel::handle_raw_data(RedPeer::InMessage* message) +{ + RedPlaybackPacket* packet = (RedPlaybackPacket*)message->data(); + uint8_t* data = (uint8_t*)(packet + 1); + uint32_t size = message->size() - sizeof(*packet); +#ifdef WAVE_CAPTURE + put_wave_data(data, size); + return; +#endif + if (size != _frame_bytes) { + //for now throw on unexpected size (based on current server imp). + // will probably be replaced by supporting flexible data size in the player imp + THROW("unexpected frame size"); + } + if ((_frame_count++ % 1000) == 0) { + get_client().set_mm_time(packet->time - _wave_player->get_delay_ms()); + } + _wave_player->write(data); +} + +void PlaybackChannel::handle_celt_data(RedPeer::InMessage* message) +{ + RedPlaybackPacket* packet = (RedPlaybackPacket*)message->data(); + uint8_t* data = (uint8_t*)(packet + 1); + uint32_t size = message->size() - sizeof(*packet); + celt_int16_t pcm[256 * 2]; + + if (celt051_decode(_celt_decoder, data, size, pcm) != CELT_OK) { + THROW("celt decode failed"); + } +#ifdef WAVE_CAPTURE + put_wave_data(pcm, _frame_bytes); + return; +#endif + if ((_frame_count++ % 1000) == 0) { + get_client().set_mm_time(packet->time - _wave_player->get_delay_ms()); + } + _wave_player->write((uint8_t *)pcm); +} + +class PlaybackFactory: public ChannelFactory { +public: + PlaybackFactory() : ChannelFactory(RED_CHANNEL_PLAYBACK) {} + virtual RedChannel* construct(RedClient& client, uint32_t id) + { + return new PlaybackChannel(client, id); + } +}; + +static PlaybackFactory factory; + +ChannelFactory& PlaybackChannel::Factory() +{ + return factory; +} + |