summaryrefslogtreecommitdiffstats
path: root/client/playback_channel.cpp
diff options
context:
space:
mode:
authorYaniv Kamay <ykamay@redhat.com>2009-09-19 21:25:46 +0300
committerYaniv Kamay <ykamay@redhat.com>2009-10-14 15:06:41 +0200
commitc1b79eb035fa158fb2ac3bc8e559809611070016 (patch)
tree3348dd749a700dedf87c9b16fe8be77c62928df8 /client/playback_channel.cpp
downloadspice-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.cpp355
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;
+}
+