summaryrefslogtreecommitdiffstats
path: root/akode/lib/player.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'akode/lib/player.cpp')
-rw-r--r--akode/lib/player.cpp650
1 files changed, 650 insertions, 0 deletions
diff --git a/akode/lib/player.cpp b/akode/lib/player.cpp
new file mode 100644
index 0000000..c785569
--- /dev/null
+++ b/akode/lib/player.cpp
@@ -0,0 +1,650 @@
+/* aKode: Player
+
+ Copyright (C) 2004-2005 Allan Sandfeld Jensen <kde@carewolf.com>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 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
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Steet, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+
+#include <pthread.h>
+#include <semaphore.h>
+#include <assert.h>
+
+#include "audioframe.h"
+#include "audiobuffer.h"
+#include "decoder.h"
+#include "buffered_decoder.h"
+#include "mmapfile.h"
+#include "localfile.h"
+#include "volumefilter.h"
+
+#include "sink.h"
+#include "converter.h"
+#include "resampler.h"
+#include "magic.h"
+
+#include "player.h"
+
+#ifndef NDEBUG
+#include <iostream>
+#define AKODE_DEBUG(x) {std::cerr << "akode: " << x << "\n";}
+#else
+#define AKODE_DEBUG(x) { }
+#endif
+
+namespace aKode {
+
+struct Player::private_data
+{
+ private_data() : src(0)
+ , frame_decoder(0)
+ , resampler(0)
+ , converter(0)
+ , volume_filter(0)
+ , sink(0)
+ , manager(0)
+ , monitor(0)
+ , decoder_plugin(0)
+ , resampler_plugin("fast")
+ , sample_rate(0)
+ , state(Closed)
+ , my_file(false)
+ , my_sink(false)
+ , start_pos(0)
+ , halt(false)
+ , pause(false)
+ , detached(false)
+ , running(false)
+ {};
+
+ File *src;
+
+ Decoder *frame_decoder;
+ BufferedDecoder buffered_decoder;
+ Resampler *resampler;
+ Converter *converter;
+ // Volatile because it can be created and destroyed during playback
+ VolumeFilter* volatile volume_filter;
+ Sink *sink;
+ Player::Manager *manager;
+ Player::Monitor *monitor;
+
+ const char* decoder_plugin;
+ const char* resampler_plugin;
+
+ SinkPluginHandler sink_handler;
+ DecoderPluginHandler decoder_handler;
+ ResamplerPluginHandler resampler_handler;
+
+ unsigned int sample_rate;
+ State state;
+ bool my_file;
+ bool my_sink;
+ int start_pos;
+
+ volatile bool halt;
+ volatile bool pause;
+ volatile bool detached;
+ bool running;
+ pthread_t player_thread;
+ sem_t pause_sem;
+
+ void setState(Player::State s) {
+ state = s;
+ if (manager) manager->stateChangeEvent(s);
+ }
+ // Called for detached players
+ void cleanup() {
+ buffered_decoder.stop();
+ buffered_decoder.closeDecoder();
+
+ delete frame_decoder;
+ if (my_file)
+ delete src;
+
+ frame_decoder = 0;
+ src = 0;
+ decoder_handler.unload();
+
+ delete resampler;
+ delete converter;
+ resampler = 0;
+ converter = 0;
+
+ delete this;
+ }
+};
+
+// The player-thread. It is controlled through the two variables
+// halt and seek_pos in d
+static void* run_player(void* arg) {
+ Player::private_data *d = (Player::private_data*)arg;
+
+ AudioFrame frame;
+ AudioFrame re_frame;
+ AudioFrame c_frame;
+ bool no_error = true;
+
+ while(true) {
+ if (d->pause) {
+ d->sink->pause();
+ sem_wait(&d->pause_sem);
+ d->sink->resume();
+ }
+ if (d->halt) break;
+
+ no_error = d->buffered_decoder.readFrame(&frame);
+
+ if (!no_error) {
+ if (d->buffered_decoder.eof())
+ goto eof;
+ else
+ if (d->buffered_decoder.error())
+ goto error;
+ else
+ AKODE_DEBUG("Blip?");
+ }
+ else {
+ AudioFrame* out_frame = &frame;
+ if (d->resampler) {
+ d->resampler->doFrame(out_frame, &re_frame);
+ out_frame = &re_frame;
+ }
+
+ if (d->converter) {
+ d->converter->doFrame(out_frame, &c_frame);
+ out_frame = &c_frame;
+ }
+
+ if (d->volume_filter)
+ d->volume_filter->doFrame(out_frame);
+
+ no_error = d->sink->writeFrame(out_frame);
+
+ if (d->monitor)
+ d->monitor->writeFrame(out_frame);
+
+ if (!no_error) {
+ // ### Check type of error
+ goto error;
+ }
+ }
+ }
+
+// Stoped by Player::stop()
+// d->buffered_decoder->stop();
+ assert(!d->detached);
+ return (void*)0;
+
+error:
+ if (d->detached) d->cleanup();
+ else {
+ d->buffered_decoder.stop();
+ if (d->manager)
+ d->manager->errorEvent();
+ }
+ return (void*)0;
+
+eof:
+ if (d->detached) d->cleanup();
+ else {
+ d->buffered_decoder.stop();
+ if (d->manager)
+ d->manager->eofEvent();
+ }
+ return (void*)0;
+}
+
+Player::Player() {
+ d = new private_data;
+ sem_init(&d->pause_sem,0,0);
+}
+
+Player::~Player() {
+ close();
+ sem_destroy(&d->pause_sem);
+ delete d;
+}
+
+bool Player::open(const char* sinkname) {
+ if (state() != Closed)
+ close();
+
+ assert(state() == Closed);
+
+ d->sink_handler.load(sinkname);
+ if (!d->sink_handler.isLoaded()) {
+ AKODE_DEBUG("Could not load " << sinkname << "-sink");
+ return false;
+ }
+ d->sink = d->sink_handler.openSink();
+ if (!d->sink) {
+ AKODE_DEBUG("Could not create " << sinkname << "-sink");
+ return false;
+ }
+ if (!d->sink->open()) {
+ AKODE_DEBUG("Could not open " << sinkname << "-sink");
+ delete d->sink;
+ d->sink = 0;
+ return false;
+ }
+ d->my_sink = true;
+ setState(Open);
+ return true;
+}
+
+bool Player::open(Sink *sink) {
+ if (state() != Closed)
+ close();
+
+ assert(state() == Closed);
+
+ d->sink = sink;
+ if (!d->sink->open()) {
+ AKODE_DEBUG("Could not open sink");
+ d->sink = 0;
+ return false;
+ }
+ d->my_sink = false;
+ setState(Open);
+ return true;
+}
+
+void Player::close() {
+ if (state() == Closed) return;
+ if (state() != Open)
+ unload();
+
+ assert(state() == Open);
+
+ delete d->volume_filter;
+ d->volume_filter = 0;
+
+ if (d->my_sink) delete d->sink;
+ d->sink = 0;
+ d->sink_handler.unload();
+ setState(Closed);
+}
+
+bool Player::load(const char* filename) {
+ if (state() == Closed) return false;
+
+ if (state() == Paused || state() == Playing)
+ stop();
+
+ if (state() == Loaded)
+ unload();
+
+ assert(state() == Open);
+
+ d->src = new MMapFile(filename);
+ // Test if the file _can_ be mmaped
+ if (!d->src->openRO()) {
+ delete d->src;
+ d->src = new LocalFile(filename);
+ if (!d->src->openRO()) {
+ AKODE_DEBUG("Could not open " << filename);
+ delete d->src;
+ d->src = 0;
+ return false;
+ }
+ }
+ // Some of the later code expects it to be closed
+ d->src->close();
+ d->my_file = true;
+
+ return load();
+}
+
+bool Player::load(File *file) {
+ if (state() == Closed) return false;
+
+ if (state() == Paused || state() == Playing)
+ stop();
+
+ if (state() == Loaded)
+ unload();
+
+ assert(state() == Open);
+
+ if (!file->openRO())
+ return false;
+ file->close();
+
+ d->src = file;
+ d->my_file = false;
+
+ return load();
+}
+
+bool Player::load() {
+ if (d->decoder_plugin) {
+ if (!d->decoder_handler.load(d->decoder_plugin))
+ AKODE_DEBUG("Could not load " << d->decoder_plugin << "-decoder");
+ }
+
+ if (!d->decoder_handler.isLoaded()) {
+ string format = Magic::detectFile(d->src);
+ if (!format.empty())
+ AKODE_DEBUG("Guessed format: " << format)
+ else {
+ AKODE_DEBUG("Cannot detect mimetype");
+ delete d->src;
+ d->src = 0;
+ return false;
+ }
+ if (!d->decoder_handler.load(format))
+ AKODE_DEBUG("Could not load " << format << "-decoder");
+ }
+
+ if (!d->decoder_handler.isLoaded()) {
+ delete d->src;
+ d->src = 0;
+ return false;
+ }
+
+ d->frame_decoder = d->decoder_handler.openDecoder(d->src);
+ if (!d->frame_decoder) {
+ AKODE_DEBUG("Failed to open Decoder");
+ d->decoder_handler.unload();
+ delete d->src;
+ d->src = 0;
+ return false;
+ }
+
+ AudioFrame first_frame;
+
+ if (!d->frame_decoder->readFrame(&first_frame)) {
+ AKODE_DEBUG("Failed to decode first frame");
+ delete d->frame_decoder;
+ d->frame_decoder = 0;
+ d->decoder_handler.unload();
+ delete d->src;
+ d->src = 0;
+ return false;
+ }
+
+ if (!loadResampler()) {
+ AKODE_DEBUG("The resampler failed to load");
+ return false;
+ }
+
+ int state = d->sink->setAudioConfiguration(&first_frame);
+ if (state < 0) {
+ AKODE_DEBUG("The sink could not be configured for this format");
+ delete d->frame_decoder;
+ d->frame_decoder = 0;
+ d->decoder_handler.unload();
+ delete d->src;
+ d->src = 0;
+ return false;
+ }
+ else
+ if (state > 0) {
+ // Configuration not 100% accurate
+ d->sample_rate = d->sink->audioConfiguration()->sample_rate;
+ if (d->sample_rate != first_frame.sample_rate) {
+ AKODE_DEBUG("Resampling to " << d->sample_rate);
+ d->resampler->setSampleRate(d->sample_rate);
+ }
+ int out_channels = d->sink->audioConfiguration()->channels;
+ int in_channels = first_frame.channels;
+ if (in_channels != out_channels) {
+ // ### We don't do mixing yet
+ AKODE_DEBUG("Sample has wrong number of channels");
+ delete d->frame_decoder;
+ d->frame_decoder = 0;
+ d->decoder_handler.unload();
+ delete d->src;
+ d->src = 0;
+ return false;
+ }
+ int out_width = d->sink->audioConfiguration()->sample_width;
+ int in_width = first_frame.sample_width;
+ if (in_width != out_width) {
+ AKODE_DEBUG("Converting to " << out_width << "bits");
+ if (!d->converter)
+ d->converter = new Converter(out_width);
+ else
+ d->converter->setSampleWidth(out_width);
+ }
+ }
+ else
+ {
+ delete d->resampler;
+ delete d->converter;
+ d->resampler = 0;
+ d->converter = 0;
+ }
+
+ // connect the streams to play
+ d->buffered_decoder.setBlockingRead(true);
+ d->buffered_decoder.openDecoder(d->frame_decoder);
+ d->buffered_decoder.buffer()->put(&first_frame);
+
+ setState(Loaded);
+
+ return true;
+}
+
+bool Player::loadResampler() {
+ if (!d->resampler) {
+ d->resampler_handler.load(d->resampler_plugin);
+ d->resampler = d->resampler_handler.openResampler();
+ }
+ return d->resampler != 0;
+}
+
+void Player::unload() {
+ if (state() == Closed || state() == Open) return;
+ if (state() == Paused || state() == Playing)
+ stop();
+
+ assert(state() == Loaded);
+
+ d->buffered_decoder.closeDecoder();
+
+ delete d->frame_decoder;
+ if (d->my_file)
+ delete d->src;
+
+ d->frame_decoder = 0;
+ d->src = 0;
+ d->decoder_handler.unload();
+
+ delete d->resampler;
+ delete d->converter;
+ d->resampler = 0;
+ d->converter = 0;
+
+ setState(Open);
+}
+
+void Player::play() {
+ if (state() == Closed || state() == Open) return;
+ if (state() == Playing) return;
+
+ if (state() == Paused) {
+ return resume();
+ }
+
+ assert(state() == Loaded);
+
+ d->frame_decoder->seek(0);
+
+ // Start buffering
+ d->buffered_decoder.start();
+
+ d->halt = d->pause = false;
+
+ if (pthread_create(&d->player_thread, 0, run_player, d) == 0) {
+ d->running = true;
+ setState(Playing);
+ } else {
+ d->running = false;
+ setState(Loaded);
+ }
+}
+
+void Player::detach() {
+ if (state() == Closed || state() == Open) return;
+ if (state() == Loaded) return;
+
+ if (state() == Paused) resume();
+
+ assert(state() == Playing);
+
+ if (d->running) {
+ pthread_detach(d->player_thread);
+ d->running = false;
+ }
+
+ private_data * d_new = new private_data;
+ d_new->sink = d->sink;
+ d_new->volume_filter = d->volume_filter;
+ d_new->sample_rate = d->sample_rate;
+ d_new->state = d->state;
+
+ d->detached = true;
+ d = d_new;
+
+ setState(Open);
+}
+
+
+void Player::stop() {
+ if (state() == Closed || state() == Open) return;
+ if (state() == Loaded) return;
+
+ // Needs to set halt first to avoid the paused thread playing a soundbite
+ d->halt = true;
+ if (state() == Paused) resume();
+
+ assert(state() == Playing);
+
+ d->buffered_decoder.stop();
+
+ if (d->running) {
+ pthread_join(d->player_thread, 0);
+ d->running = false;
+ }
+
+ setState(Loaded);
+}
+
+// Much like stop except we don't send a halt signal
+void Player::wait() {
+ if (state() == Closed || state() == Open) return;
+ if (state() == Loaded) return;
+
+ if (state() == Paused) resume();
+
+ assert(state() == Playing);
+
+ if (d->running) {
+ pthread_join(d->player_thread, 0);
+ d->running = false;
+ }
+
+ setState(Loaded);
+}
+
+void Player::pause() {
+ if (state() == Closed || state() == Open || state() == Loaded) return;
+ if (state() == Paused) return;
+
+ assert(state() == Playing);
+
+ //d->buffer->pause();
+ d->pause = true;
+ setState(Paused);
+}
+
+void Player::resume() {
+ if (state() != Paused) return;
+
+ d->pause = false;
+ sem_post(&d->pause_sem);
+
+ setState(Playing);
+}
+
+
+void Player::setVolume(float f) {
+ if (f < 0.0 || f > 1.0) return;
+
+ if (f != 1.0 && !d->volume_filter) {
+ VolumeFilter *vf = new VolumeFilter();
+ vf->setVolume(f);
+ d->volume_filter = vf;
+ }
+ else if (f != 1.0) {
+ d->volume_filter->setVolume(f);
+ }
+ else if (d->volume_filter) {
+ VolumeFilter *f = d->volume_filter;
+ d->volume_filter = 0;
+ delete f;
+ }
+}
+
+float Player::volume() const {
+ if (!d->volume_filter) return 1.0;
+ else
+ return d->volume_filter->volume();
+}
+
+File* Player::file() const {
+ return d->src;
+}
+
+Sink* Player::sink() const {
+ return d->sink;
+}
+
+Decoder* Player::decoder() const {
+ return &d->buffered_decoder;
+}
+
+Resampler* Player::resampler() const {
+ return d->resampler;
+}
+
+Player::State Player::state() const {
+ return d->state;
+}
+
+void Player::setDecoderPlugin(const char* plugin) {
+ if (plugin && strncmp(plugin, "auto", 4) == 0) plugin = 0;
+ d->decoder_plugin = plugin;
+}
+
+void Player::setResamplerPlugin(const char* plugin) {
+ d->resampler_plugin = plugin;
+}
+
+void Player::setManager(Manager *manager) {
+ d->manager = manager;
+}
+
+void Player::setMonitor(Monitor *monitor) {
+ d->monitor = monitor;
+}
+
+void Player::setState(Player::State state) {
+ d->setState(state);
+}
+
+} // namespace