Input*: move to input/

This commit is contained in:
Max Kellermann
2014-01-24 16:18:21 +01:00
parent e199c33c6e
commit f8bfea8bae
76 changed files with 114 additions and 117 deletions

View File

@@ -0,0 +1,431 @@
/*
* Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* 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.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
/*
* ALSA code based on an example by Paul Davis released under GPL here:
* http://equalarea.com/paul/alsa-audio.html
* and one by Matthias Nagorni, also GPL, here:
* http://alsamodular.sourceforge.net/alsa_programming_howto.html
*/
#include "config.h"
#include "AlsaInputPlugin.hxx"
#include "../InputPlugin.hxx"
#include "../InputStream.hxx"
#include "util/Domain.hxx"
#include "util/Error.hxx"
#include "util/StringUtil.hxx"
#include "util/ReusableArray.hxx"
#include "util/Cast.hxx"
#include "Log.hxx"
#include "event/MultiSocketMonitor.hxx"
#include "event/DeferredMonitor.hxx"
#include "event/Call.hxx"
#include "thread/Mutex.hxx"
#include "thread/Cond.hxx"
#include "IOThread.hxx"
#include <alsa/asoundlib.h>
#include <assert.h>
#include <string.h>
static constexpr Domain alsa_input_domain("alsa");
static constexpr const char *default_device = "hw:0,0";
// the following defaults are because the PcmDecoderPlugin forces CD format
static constexpr snd_pcm_format_t default_format = SND_PCM_FORMAT_S16;
static constexpr int default_channels = 2; // stereo
static constexpr unsigned int default_rate = 44100; // cd quality
/**
* This value should be the same as the read buffer size defined in
* PcmDecoderPlugin.cxx:pcm_stream_decode().
* We use it to calculate how many audio frames to buffer in the alsa driver
* before reading from the device. snd_pcm_readi() blocks until that many
* frames are ready.
*/
static constexpr size_t read_buffer_size = 4096;
class AlsaInputStream final : MultiSocketMonitor, DeferredMonitor {
InputStream base;
snd_pcm_t *capture_handle;
size_t frame_size;
int frames_to_read;
bool eof;
/**
* Is somebody waiting for data? This is set by method
* Available().
*/
std::atomic_bool waiting;
ReusableArray<pollfd> pfd_buffer;
public:
AlsaInputStream(EventLoop &loop,
const char *uri, Mutex &mutex, Cond &cond,
snd_pcm_t *_handle, int _frame_size)
:MultiSocketMonitor(loop),
DeferredMonitor(loop),
base(input_plugin_alsa, uri, mutex, cond),
capture_handle(_handle),
frame_size(_frame_size),
eof(false)
{
assert(uri != nullptr);
assert(_handle != nullptr);
/* this mime type forces use of the PcmDecoderPlugin.
Needs to be generalised when/if that decoder is
updated to support other audio formats */
base.mime = "audio/x-mpd-cdda-pcm";
base.seekable = false;
base.size = -1;
base.ready = true;
frames_to_read = read_buffer_size / frame_size;
snd_pcm_start(capture_handle);
DeferredMonitor::Schedule();
}
~AlsaInputStream() {
snd_pcm_close(capture_handle);
}
using DeferredMonitor::GetEventLoop;
static InputStream *Create(const char *uri, Mutex &mutex, Cond &cond,
Error &error);
#if GCC_CHECK_VERSION(4,6) || defined(__clang__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Winvalid-offsetof"
#endif
static constexpr AlsaInputStream *Cast(InputStream *is) {
return ContainerCast(is, AlsaInputStream, base);
}
#if GCC_CHECK_VERSION(4,6) || defined(__clang__)
#pragma GCC diagnostic pop
#endif
bool Available() {
if (snd_pcm_avail(capture_handle) > frames_to_read)
return true;
if (!waiting.exchange(true))
SafeInvalidateSockets();
return false;
}
size_t Read(void *ptr, size_t size, Error &error);
bool IsEOF() {
return eof;
}
private:
static snd_pcm_t *OpenDevice(const char *device, int rate,
snd_pcm_format_t format, int channels,
Error &error);
int Recover(int err);
void SafeInvalidateSockets() {
DeferredMonitor::Schedule();
}
virtual void RunDeferred() override {
InvalidateSockets();
}
virtual int PrepareSockets() override;
virtual void DispatchSockets() override;
};
inline InputStream *
AlsaInputStream::Create(const char *uri, Mutex &mutex, Cond &cond,
Error &error)
{
const char *const scheme = "alsa://";
if (!StringStartsWith(uri, scheme))
return nullptr;
const char *device = uri + strlen(scheme);
if (strlen(device) == 0)
device = default_device;
/* placeholders - eventually user-requested audio format will
be passed via the URI. For now we just force the
defaults */
int rate = default_rate;
snd_pcm_format_t format = default_format;
int channels = default_channels;
snd_pcm_t *handle = OpenDevice(device, rate, format, channels,
error);
if (handle == nullptr)
return nullptr;
int frame_size = snd_pcm_format_width(format) / 8 * channels;
AlsaInputStream *stream = new AlsaInputStream(io_thread_get(),
uri, mutex, cond,
handle, frame_size);
return &stream->base;
}
inline size_t
AlsaInputStream::Read(void *ptr, size_t size, Error &error)
{
assert(ptr != nullptr);
int num_frames = size / frame_size;
int ret;
while ((ret = snd_pcm_readi(capture_handle, ptr, num_frames)) < 0) {
if (Recover(ret) < 0) {
eof = true;
error.Format(alsa_input_domain,
"PCM error - stream aborted");
return 0;
}
}
size_t nbytes = ret * frame_size;
base.offset += nbytes;
return nbytes;
}
int
AlsaInputStream::PrepareSockets()
{
if (!waiting) {
ClearSocketList();
return -1;
}
int count = snd_pcm_poll_descriptors_count(capture_handle);
if (count < 0) {
ClearSocketList();
return -1;
}
struct pollfd *pfds = pfd_buffer.Get(count);
count = snd_pcm_poll_descriptors(capture_handle, pfds, count);
if (count < 0)
count = 0;
ReplaceSocketList(pfds, count);
return -1;
}
void
AlsaInputStream::DispatchSockets()
{
waiting = false;
const ScopeLock protect(base.mutex);
/* wake up the thread that is waiting for more data */
base.cond.broadcast();
}
inline int
AlsaInputStream::Recover(int err)
{
switch(err) {
case -EPIPE:
LogDebug(alsa_input_domain, "Buffer Overrun");
// drop through
case -ESTRPIPE:
case -EINTR:
err = snd_pcm_recover(capture_handle, err, 1);
break;
default:
// something broken somewhere, give up
err = -1;
}
return err;
}
inline snd_pcm_t *
AlsaInputStream::OpenDevice(const char *device,
int rate, snd_pcm_format_t format, int channels,
Error &error)
{
snd_pcm_t *capture_handle;
int err;
if ((err = snd_pcm_open(&capture_handle, device,
SND_PCM_STREAM_CAPTURE, 0)) < 0) {
error.Format(alsa_input_domain, "Failed to open device: %s (%s)", device, snd_strerror(err));
return nullptr;
}
snd_pcm_hw_params_t *hw_params;
if ((err = snd_pcm_hw_params_malloc(&hw_params)) < 0) {
error.Format(alsa_input_domain, "Cannot allocate hardware parameter structure (%s)", snd_strerror(err));
snd_pcm_close(capture_handle);
return nullptr;
}
if ((err = snd_pcm_hw_params_any(capture_handle, hw_params)) < 0) {
error.Format(alsa_input_domain, "Cannot initialize hardware parameter structure (%s)", snd_strerror(err));
snd_pcm_hw_params_free(hw_params);
snd_pcm_close(capture_handle);
return nullptr;
}
if ((err = snd_pcm_hw_params_set_access(capture_handle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) {
error.Format(alsa_input_domain, "Cannot set access type (%s)", snd_strerror (err));
snd_pcm_hw_params_free(hw_params);
snd_pcm_close(capture_handle);
return nullptr;
}
if ((err = snd_pcm_hw_params_set_format(capture_handle, hw_params, format)) < 0) {
snd_pcm_hw_params_free(hw_params);
snd_pcm_close(capture_handle);
error.Format(alsa_input_domain, "Cannot set sample format (%s)", snd_strerror (err));
return nullptr;
}
if ((err = snd_pcm_hw_params_set_channels(capture_handle, hw_params, channels)) < 0) {
snd_pcm_hw_params_free(hw_params);
snd_pcm_close(capture_handle);
error.Format(alsa_input_domain, "Cannot set channels (%s)", snd_strerror (err));
return nullptr;
}
if ((err = snd_pcm_hw_params_set_rate(capture_handle, hw_params, rate, 0)) < 0) {
snd_pcm_hw_params_free(hw_params);
snd_pcm_close(capture_handle);
error.Format(alsa_input_domain, "Cannot set sample rate (%s)", snd_strerror (err));
return nullptr;
}
/* period needs to be big enough so that poll() doesn't fire too often,
* but small enough that buffer overruns don't occur if Read() is not
* invoked often enough.
* the calculation here is empirical; however all measurements were
* done using 44100:16:2. When we extend this plugin to support
* other audio formats then this may need to be revisited */
snd_pcm_uframes_t period = read_buffer_size * 2;
int direction = -1;
if ((err = snd_pcm_hw_params_set_period_size_near(capture_handle, hw_params,
&period, &direction)) < 0) {
error.Format(alsa_input_domain, "Cannot set period size (%s)",
snd_strerror(err));
snd_pcm_hw_params_free(hw_params);
snd_pcm_close(capture_handle);
return nullptr;
}
if ((err = snd_pcm_hw_params(capture_handle, hw_params)) < 0) {
error.Format(alsa_input_domain, "Cannot set parameters (%s)",
snd_strerror(err));
snd_pcm_hw_params_free(hw_params);
snd_pcm_close(capture_handle);
return nullptr;
}
snd_pcm_hw_params_free (hw_params);
snd_pcm_sw_params_t *sw_params;
snd_pcm_sw_params_malloc(&sw_params);
snd_pcm_sw_params_current(capture_handle, sw_params);
if ((err = snd_pcm_sw_params_set_start_threshold(capture_handle, sw_params,
period)) < 0) {
error.Format(alsa_input_domain,
"unable to set start threshold (%s)", snd_strerror(err));
snd_pcm_sw_params_free(sw_params);
snd_pcm_close(capture_handle);
return nullptr;
}
if ((err = snd_pcm_sw_params(capture_handle, sw_params)) < 0) {
error.Format(alsa_input_domain,
"unable to install sw params (%s)", snd_strerror(err));
snd_pcm_sw_params_free(sw_params);
snd_pcm_close(capture_handle);
return nullptr;
}
snd_pcm_sw_params_free(sw_params);
snd_pcm_prepare(capture_handle);
return capture_handle;
}
/*######################### Plugin Functions ##############################*/
static InputStream *
alsa_input_open(const char *uri, Mutex &mutex, Cond &cond, Error &error)
{
return AlsaInputStream::Create(uri, mutex, cond, error);
}
static void
alsa_input_close(InputStream *is)
{
AlsaInputStream *ais = AlsaInputStream::Cast(is);
delete ais;
}
static bool
alsa_input_available(InputStream *is)
{
AlsaInputStream *ais = AlsaInputStream::Cast(is);
return ais->Available();
}
static size_t
alsa_input_read(InputStream *is, void *ptr, size_t size, Error &error)
{
AlsaInputStream *ais = AlsaInputStream::Cast(is);
return ais->Read(ptr, size, error);
}
static bool
alsa_input_eof(gcc_unused InputStream *is)
{
AlsaInputStream *ais = AlsaInputStream::Cast(is);
return ais->IsEOF();
}
const struct InputPlugin input_plugin_alsa = {
"alsa",
nullptr,
nullptr,
alsa_input_open,
alsa_input_close,
nullptr,
nullptr,
nullptr,
alsa_input_available,
alsa_input_read,
alsa_input_eof,
nullptr,
};

View File

@@ -0,0 +1,28 @@
/*
* Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* 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.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifndef MPD_ALSA_INPUT_PLUGIN_HXX
#define MPD_ALSA_INPUT_PLUGIN_HXX
#include "../InputPlugin.hxx"
extern const struct InputPlugin input_plugin_alsa;
#endif

View File

@@ -0,0 +1,99 @@
/*
* Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* 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.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "config.h"
#include "ArchiveInputPlugin.hxx"
#include "archive/ArchiveDomain.hxx"
#include "archive/ArchiveLookup.hxx"
#include "archive/ArchiveList.hxx"
#include "archive/ArchivePlugin.hxx"
#include "archive/ArchiveFile.hxx"
#include "../InputPlugin.hxx"
#include "fs/Traits.hxx"
#include "util/Alloc.hxx"
#include "Log.hxx"
#include <stdlib.h>
/**
* select correct archive plugin to handle the input stream
* may allow stacking of archive plugins. for example for handling
* tar.gz a gzip handler opens file (through inputfile stream)
* then it opens a tar handler and sets gzip inputstream as
* parent_stream so tar plugin fetches file data from gzip
* plugin and gzip fetches file from disk
*/
static InputStream *
input_archive_open(const char *pathname,
Mutex &mutex, Cond &cond,
Error &error)
{
const struct archive_plugin *arplug;
InputStream *is;
if (!PathTraitsFS::IsAbsolute(pathname))
return nullptr;
char *pname = strdup(pathname);
// archive_lookup will modify pname when true is returned
const char *archive, *filename, *suffix;
if (!archive_lookup(pname, &archive, &filename, &suffix)) {
FormatDebug(archive_domain,
"not an archive, lookup %s failed", pname);
free(pname);
return nullptr;
}
//check which archive plugin to use (by ext)
arplug = archive_plugin_from_suffix(suffix);
if (!arplug) {
FormatWarning(archive_domain,
"can't handle archive %s", archive);
free(pname);
return nullptr;
}
auto file = archive_file_open(arplug, archive, error);
if (file == nullptr) {
free(pname);
return nullptr;
}
//setup fileops
is = file->OpenStream(filename, mutex, cond, error);
free(pname);
file->Close();
return is;
}
const InputPlugin input_plugin_archive = {
"archive",
nullptr,
nullptr,
input_archive_open,
nullptr,
nullptr,
nullptr,
nullptr,
nullptr,
nullptr,
nullptr,
nullptr,
};

View File

@@ -0,0 +1,25 @@
/*
* Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* 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.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifndef MPD_INPUT_ARCHIVE_HXX
#define MPD_INPUT_ARCHIVE_HXX
extern const struct InputPlugin input_plugin_archive;
#endif

View File

@@ -0,0 +1,407 @@
/*
* Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* 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.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
/**
* CD-Audio handling (requires libcdio_paranoia)
*/
#include "config.h"
#include "CdioParanoiaInputPlugin.hxx"
#include "../InputStream.hxx"
#include "../InputPlugin.hxx"
#include "util/StringUtil.hxx"
#include "util/Error.hxx"
#include "util/Domain.hxx"
#include "system/ByteOrder.hxx"
#include "fs/AllocatedPath.hxx"
#include "Log.hxx"
#include "config/ConfigData.hxx"
#include "config/ConfigError.hxx"
#include <stdio.h>
#include <stdint.h>
#include <stddef.h>
#include <string.h>
#include <stdlib.h>
#include <glib.h>
#include <assert.h>
#ifdef HAVE_CDIO_PARANOIA_PARANOIA_H
#include <cdio/paranoia/paranoia.h>
#else
#include <cdio/paranoia.h>
#endif
#include <cdio/cd_types.h>
struct CdioParanoiaInputStream {
InputStream base;
cdrom_drive_t *drv;
CdIo_t *cdio;
cdrom_paranoia_t *para;
lsn_t lsn_from, lsn_to;
int lsn_relofs;
int trackno;
char buffer[CDIO_CD_FRAMESIZE_RAW];
int buffer_lsn;
CdioParanoiaInputStream(const char *uri, Mutex &mutex, Cond &cond,
int _trackno)
:base(input_plugin_cdio_paranoia, uri, mutex, cond),
drv(nullptr), cdio(nullptr), para(nullptr),
trackno(_trackno)
{
}
~CdioParanoiaInputStream() {
if (para != nullptr)
cdio_paranoia_free(para);
if (drv != nullptr)
cdio_cddap_close_no_free_cdio(drv);
if (cdio != nullptr)
cdio_destroy(cdio);
}
};
static constexpr Domain cdio_domain("cdio");
static bool default_reverse_endian;
static bool
input_cdio_init(const config_param &param, Error &error)
{
const char *value = param.GetBlockValue("default_byte_order");
if (value != nullptr) {
if (strcmp(value, "little_endian") == 0)
default_reverse_endian = IsBigEndian();
else if (strcmp(value, "big_endian") == 0)
default_reverse_endian = IsLittleEndian();
else {
error.Format(config_domain, 0,
"Unrecognized 'default_byte_order' setting: %s",
value);
return false;
}
}
return true;
}
static void
input_cdio_close(InputStream *is)
{
CdioParanoiaInputStream *i = (CdioParanoiaInputStream *)is;
delete i;
}
struct cdio_uri {
char device[64];
int track;
};
static bool
parse_cdio_uri(struct cdio_uri *dest, const char *src, Error &error)
{
if (!StringStartsWith(src, "cdda://"))
return false;
src += 7;
if (*src == 0) {
/* play the whole CD in the default drive */
dest->device[0] = 0;
dest->track = -1;
return true;
}
const char *slash = strrchr(src, '/');
if (slash == nullptr) {
/* play the whole CD in the specified drive */
g_strlcpy(dest->device, src, sizeof(dest->device));
dest->track = -1;
return true;
}
size_t device_length = slash - src;
if (device_length >= sizeof(dest->device))
device_length = sizeof(dest->device) - 1;
memcpy(dest->device, src, device_length);
dest->device[device_length] = 0;
const char *track = slash + 1;
char *endptr;
dest->track = strtoul(track, &endptr, 10);
if (*endptr != 0) {
error.Set(cdio_domain, "Malformed track number");
return false;
}
if (endptr == track)
/* play the whole CD */
dest->track = -1;
return true;
}
static AllocatedPath
cdio_detect_device(void)
{
char **devices = cdio_get_devices_with_cap(nullptr, CDIO_FS_AUDIO,
false);
if (devices == nullptr)
return AllocatedPath::Null();
AllocatedPath path = AllocatedPath::FromFS(devices[0]);
cdio_free_device_list(devices);
return path;
}
static InputStream *
input_cdio_open(const char *uri,
Mutex &mutex, Cond &cond,
Error &error)
{
struct cdio_uri parsed_uri;
if (!parse_cdio_uri(&parsed_uri, uri, error))
return nullptr;
CdioParanoiaInputStream *i =
new CdioParanoiaInputStream(uri, mutex, cond,
parsed_uri.track);
/* get list of CD's supporting CD-DA */
const AllocatedPath device = parsed_uri.device[0] != 0
? AllocatedPath::FromFS(parsed_uri.device)
: cdio_detect_device();
if (device.IsNull()) {
error.Set(cdio_domain,
"Unable find or access a CD-ROM drive with an audio CD in it.");
delete i;
return nullptr;
}
/* Found such a CD-ROM with a CD-DA loaded. Use the first drive in the list. */
i->cdio = cdio_open(device.c_str(), DRIVER_UNKNOWN);
i->drv = cdio_cddap_identify_cdio(i->cdio, 1, nullptr);
if ( !i->drv ) {
error.Set(cdio_domain, "Unable to identify audio CD disc.");
delete i;
return nullptr;
}
cdda_verbose_set(i->drv, CDDA_MESSAGE_FORGETIT, CDDA_MESSAGE_FORGETIT);
if ( 0 != cdio_cddap_open(i->drv) ) {
error.Set(cdio_domain, "Unable to open disc.");
delete i;
return nullptr;
}
bool reverse_endian;
switch (data_bigendianp(i->drv)) {
case -1:
LogDebug(cdio_domain, "drive returns unknown audio data");
reverse_endian = default_reverse_endian;
break;
case 0:
LogDebug(cdio_domain, "drive returns audio data Little Endian");
reverse_endian = IsBigEndian();
break;
case 1:
LogDebug(cdio_domain, "drive returns audio data Big Endian");
reverse_endian = IsLittleEndian();
break;
default:
error.Format(cdio_domain, "Drive returns unknown data type %d",
data_bigendianp(i->drv));
delete i;
return nullptr;
}
i->lsn_relofs = 0;
if (i->trackno >= 0) {
i->lsn_from = cdio_get_track_lsn(i->cdio, i->trackno);
i->lsn_to = cdio_get_track_last_lsn(i->cdio, i->trackno);
} else {
i->lsn_from = 0;
i->lsn_to = cdio_get_disc_last_lsn(i->cdio);
}
i->para = cdio_paranoia_init(i->drv);
/* Set reading mode for full paranoia, but allow skipping sectors. */
paranoia_modeset(i->para, PARANOIA_MODE_FULL^PARANOIA_MODE_NEVERSKIP);
/* seek to beginning of the track */
cdio_paranoia_seek(i->para, i->lsn_from, SEEK_SET);
i->base.ready = true;
i->base.seekable = true;
i->base.size = (i->lsn_to - i->lsn_from + 1) * CDIO_CD_FRAMESIZE_RAW;
/* hack to make MPD select the "pcm" decoder plugin */
i->base.mime = reverse_endian
? "audio/x-mpd-cdda-pcm-reverse"
: "audio/x-mpd-cdda-pcm";
return &i->base;
}
static bool
input_cdio_seek(InputStream *is,
InputPlugin::offset_type offset, int whence, Error &error)
{
CdioParanoiaInputStream *cis = (CdioParanoiaInputStream *)is;
/* calculate absolute offset */
switch (whence) {
case SEEK_SET:
break;
case SEEK_CUR:
offset += cis->base.offset;
break;
case SEEK_END:
offset += cis->base.size;
break;
}
if (offset < 0 || offset > cis->base.size) {
error.Format(cdio_domain, "Invalid offset to seek %ld (%ld)",
(long int)offset, (long int)cis->base.size);
return false;
}
/* simple case */
if (offset == cis->base.offset)
return true;
/* calculate current LSN */
cis->lsn_relofs = offset / CDIO_CD_FRAMESIZE_RAW;
cis->base.offset = offset;
cdio_paranoia_seek(cis->para, cis->lsn_from + cis->lsn_relofs, SEEK_SET);
return true;
}
static size_t
input_cdio_read(InputStream *is, void *ptr, size_t length,
Error &error)
{
CdioParanoiaInputStream *cis = (CdioParanoiaInputStream *)is;
size_t nbytes = 0;
int diff;
size_t len, maxwrite;
int16_t *rbuf;
char *s_err, *s_mess;
char *wptr = (char *) ptr;
while (length > 0) {
/* end of track ? */
if (cis->lsn_from + cis->lsn_relofs > cis->lsn_to)
break;
//current sector was changed ?
if (cis->lsn_relofs != cis->buffer_lsn) {
rbuf = cdio_paranoia_read(cis->para, nullptr);
s_err = cdda_errors(cis->drv);
if (s_err) {
FormatError(cdio_domain,
"paranoia_read: %s", s_err);
free(s_err);
}
s_mess = cdda_messages(cis->drv);
if (s_mess) {
free(s_mess);
}
if (!rbuf) {
error.Set(cdio_domain,
"paranoia read error. Stopping.");
return 0;
}
//store current buffer
memcpy(cis->buffer, rbuf, CDIO_CD_FRAMESIZE_RAW);
cis->buffer_lsn = cis->lsn_relofs;
} else {
//use cached sector
rbuf = (int16_t*) cis->buffer;
}
//correct offset
diff = cis->base.offset - cis->lsn_relofs * CDIO_CD_FRAMESIZE_RAW;
assert(diff >= 0 && diff < CDIO_CD_FRAMESIZE_RAW);
maxwrite = CDIO_CD_FRAMESIZE_RAW - diff; //# of bytes pending in current buffer
len = (length < maxwrite? length : maxwrite);
//skip diff bytes from this lsn
memcpy(wptr, ((char*)rbuf) + diff, len);
//update pointer
wptr += len;
nbytes += len;
//update offset
cis->base.offset += len;
cis->lsn_relofs = cis->base.offset / CDIO_CD_FRAMESIZE_RAW;
//update length
length -= len;
}
return nbytes;
}
static bool
input_cdio_eof(InputStream *is)
{
CdioParanoiaInputStream *cis = (CdioParanoiaInputStream *)is;
return (cis->lsn_from + cis->lsn_relofs > cis->lsn_to);
}
const InputPlugin input_plugin_cdio_paranoia = {
"cdio_paranoia",
input_cdio_init,
nullptr,
input_cdio_open,
input_cdio_close,
nullptr,
nullptr,
nullptr,
nullptr,
input_cdio_read,
input_cdio_eof,
input_cdio_seek,
};

View File

@@ -0,0 +1,28 @@
/*
* Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* 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.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifndef MPD_CDIO_PARANOIA_INPUT_PLUGIN_HXX
#define MPD_CDIO_PARANOIA_INPUT_PLUGIN_HXX
/**
* An input plugin based on libcdio_paranoia library.
*/
extern const struct InputPlugin input_plugin_cdio_paranoia;
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,25 @@
/*
* Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* 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.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifndef MPD_INPUT_CURL_HXX
#define MPD_INPUT_CURL_HXX
extern const struct InputPlugin input_plugin_curl;
#endif

View File

@@ -0,0 +1,262 @@
/*
* Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* 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.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "config.h"
#include "DespotifyInputPlugin.hxx"
#include "DespotifyUtils.hxx"
#include "../InputStream.hxx"
#include "../InputPlugin.hxx"
#include "tag/Tag.hxx"
#include "util/StringUtil.hxx"
#include "Log.hxx"
extern "C" {
#include <despotify.h>
}
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <stdio.h>
class DespotifyInputStream {
InputStream base;
struct despotify_session *session;
struct ds_track *track;
Tag tag;
struct ds_pcm_data pcm;
size_t len_available;
bool eof;
DespotifyInputStream(const char *uri,
Mutex &mutex, Cond &cond,
despotify_session *_session,
ds_track *_track)
:base(input_plugin_despotify, uri, mutex, cond),
session(_session), track(_track),
tag(mpd_despotify_tag_from_track(*track)),
len_available(0), eof(false) {
memset(&pcm, 0, sizeof(pcm));
/* Despotify outputs pcm data */
base.mime = "audio/x-mpd-cdda-pcm";
base.ready = true;
}
public:
~DespotifyInputStream() {
despotify_free_track(track);
}
static InputStream *Open(const char *url, Mutex &mutex, Cond &cond,
Error &error);
bool IsEOF() const {
return eof;
}
size_t Read(void *ptr, size_t size, Error &error);
Tag *ReadTag() {
if (tag.IsEmpty())
return nullptr;
Tag *result = new Tag(std::move(tag));
tag.Clear();
return result;
}
void Callback(int sig);
private:
void FillBuffer();
};
inline void
DespotifyInputStream::FillBuffer()
{
/* Wait until there is data */
while (1) {
int rc = despotify_get_pcm(session, &pcm);
if (rc == 0 && pcm.len) {
len_available = pcm.len;
break;
}
if (eof == true)
break;
if (rc < 0) {
LogDebug(despotify_domain, "despotify_get_pcm error");
eof = true;
break;
}
/* Wait a while until next iteration */
usleep(50 * 1000);
}
}
inline void
DespotifyInputStream::Callback(int sig)
{
switch (sig) {
case DESPOTIFY_NEW_TRACK:
break;
case DESPOTIFY_TIME_TELL:
break;
case DESPOTIFY_TRACK_PLAY_ERROR:
LogWarning(despotify_domain, "Track play error");
eof = true;
len_available = 0;
break;
case DESPOTIFY_END_OF_PLAYLIST:
eof = true;
LogDebug(despotify_domain, "End of playlist");
break;
}
}
static void callback(gcc_unused struct despotify_session* ds,
int sig, gcc_unused void* data, void* callback_data)
{
DespotifyInputStream *ctx = (DespotifyInputStream *)callback_data;
ctx->Callback(sig);
}
inline InputStream *
DespotifyInputStream::Open(const char *url,
Mutex &mutex, Cond &cond,
gcc_unused Error &error)
{
if (!StringStartsWith(url, "spt://"))
return nullptr;
despotify_session *session = mpd_despotify_get_session();
if (session == nullptr)
return nullptr;
ds_link *ds_link = despotify_link_from_uri(url + 6);
if (!ds_link) {
FormatDebug(despotify_domain, "Can't find %s", url);
return nullptr;
}
if (ds_link->type != LINK_TYPE_TRACK) {
despotify_free_link(ds_link);
return nullptr;
}
ds_track *track = despotify_link_get_track(session, ds_link);
despotify_free_link(ds_link);
if (!track)
return nullptr;
DespotifyInputStream *ctx =
new DespotifyInputStream(url, mutex, cond,
session, track);
if (!mpd_despotify_register_callback(callback, ctx)) {
delete ctx;
return nullptr;
}
if (despotify_play(ctx->session, ctx->track, false) == false) {
mpd_despotify_unregister_callback(callback);
delete ctx;
return nullptr;
}
return &ctx->base;
}
static InputStream *
input_despotify_open(const char *url, Mutex &mutex, Cond &cond, Error &error)
{
return DespotifyInputStream::Open(url, mutex, cond, error);
}
inline size_t
DespotifyInputStream::Read(void *ptr, size_t size, gcc_unused Error &error)
{
if (len_available == 0)
FillBuffer();
size_t to_cpy = std::min(size, len_available);
memcpy(ptr, pcm.buf, to_cpy);
len_available -= to_cpy;
base.offset += to_cpy;
return to_cpy;
}
static size_t
input_despotify_read(InputStream *is, void *ptr, size_t size, Error &error)
{
DespotifyInputStream *ctx = (DespotifyInputStream *)is;
return ctx->Read(ptr, size, error);
}
static void
input_despotify_close(InputStream *is)
{
DespotifyInputStream *ctx = (DespotifyInputStream *)is;
mpd_despotify_unregister_callback(callback);
delete ctx;
}
static bool
input_despotify_eof(InputStream *is)
{
DespotifyInputStream *ctx = (DespotifyInputStream *)is;
return ctx->IsEOF();
}
static Tag *
input_despotify_tag(InputStream *is)
{
DespotifyInputStream *ctx = (DespotifyInputStream *)is;
return ctx->ReadTag();
}
const InputPlugin input_plugin_despotify = {
"despotify",
nullptr,
nullptr,
input_despotify_open,
input_despotify_close,
nullptr,
nullptr,
input_despotify_tag,
nullptr,
input_despotify_read,
input_despotify_eof,
nullptr,
};

View File

@@ -0,0 +1,25 @@
/*
* Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* 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.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifndef INPUT_DESPOTIFY_HXX
#define INPUT_DESPOTIFY_HXX
extern const struct InputPlugin input_plugin_despotify;
#endif

View File

@@ -0,0 +1,177 @@
/*
* Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* 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.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
/* necessary because libavutil/common.h uses UINT64_C */
#define __STDC_CONSTANT_MACROS
#include "config.h"
#include "FfmpegInputPlugin.hxx"
#include "../InputStream.hxx"
#include "../InputPlugin.hxx"
#include "util/StringUtil.hxx"
#include "util/Error.hxx"
#include "util/Domain.hxx"
extern "C" {
#include <libavformat/avio.h>
#include <libavformat/avformat.h>
}
struct FfmpegInputStream {
InputStream base;
AVIOContext *h;
bool eof;
FfmpegInputStream(const char *uri, Mutex &mutex, Cond &cond,
AVIOContext *_h)
:base(input_plugin_ffmpeg, uri, mutex, cond),
h(_h), eof(false) {
base.ready = true;
base.seekable = (h->seekable & AVIO_SEEKABLE_NORMAL) != 0;
base.size = avio_size(h);
/* hack to make MPD select the "ffmpeg" decoder plugin
- since avio.h doesn't tell us the MIME type of the
resource, we can't select a decoder plugin, but the
"ffmpeg" plugin is quite good at auto-detection */
base.mime = "audio/x-mpd-ffmpeg";
}
~FfmpegInputStream() {
avio_close(h);
}
};
static constexpr Domain ffmpeg_domain("ffmpeg");
static inline bool
input_ffmpeg_supported(void)
{
void *opaque = nullptr;
return avio_enum_protocols(&opaque, 0) != nullptr;
}
static bool
input_ffmpeg_init(gcc_unused const config_param &param,
Error &error)
{
av_register_all();
/* disable this plugin if there's no registered protocol */
if (!input_ffmpeg_supported()) {
error.Set(ffmpeg_domain, "No protocol");
return false;
}
return true;
}
static InputStream *
input_ffmpeg_open(const char *uri,
Mutex &mutex, Cond &cond,
Error &error)
{
if (!StringStartsWith(uri, "gopher://") &&
!StringStartsWith(uri, "rtp://") &&
!StringStartsWith(uri, "rtsp://") &&
!StringStartsWith(uri, "rtmp://") &&
!StringStartsWith(uri, "rtmpt://") &&
!StringStartsWith(uri, "rtmps://"))
return nullptr;
AVIOContext *h;
int ret = avio_open(&h, uri, AVIO_FLAG_READ);
if (ret != 0) {
error.Set(ffmpeg_domain, ret,
"libavformat failed to open the URI");
return nullptr;
}
auto *i = new FfmpegInputStream(uri, mutex, cond, h);
return &i->base;
}
static size_t
input_ffmpeg_read(InputStream *is, void *ptr, size_t size,
Error &error)
{
FfmpegInputStream *i = (FfmpegInputStream *)is;
int ret = avio_read(i->h, (unsigned char *)ptr, size);
if (ret <= 0) {
if (ret < 0)
error.Set(ffmpeg_domain, "avio_read() failed");
i->eof = true;
return false;
}
is->offset += ret;
return (size_t)ret;
}
static void
input_ffmpeg_close(InputStream *is)
{
FfmpegInputStream *i = (FfmpegInputStream *)is;
delete i;
}
static bool
input_ffmpeg_eof(InputStream *is)
{
FfmpegInputStream *i = (FfmpegInputStream *)is;
return i->eof;
}
static bool
input_ffmpeg_seek(InputStream *is, InputPlugin::offset_type offset,
int whence,
Error &error)
{
FfmpegInputStream *i = (FfmpegInputStream *)is;
int64_t ret = avio_seek(i->h, offset, whence);
if (ret >= 0) {
i->eof = false;
return true;
} else {
error.Set(ffmpeg_domain, "avio_seek() failed");
return false;
}
}
const InputPlugin input_plugin_ffmpeg = {
"ffmpeg",
input_ffmpeg_init,
nullptr,
input_ffmpeg_open,
input_ffmpeg_close,
nullptr,
nullptr,
nullptr,
nullptr,
input_ffmpeg_read,
input_ffmpeg_eof,
input_ffmpeg_seek,
};

View File

@@ -0,0 +1,28 @@
/*
* Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* 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.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifndef MPD_FFMPEG_INPUT_PLUGIN_HXX
#define MPD_FFMPEG_INPUT_PLUGIN_HXX
/**
* An input plugin based on libavformat's "avio" library.
*/
extern const struct InputPlugin input_plugin_ffmpeg;
#endif

View File

@@ -0,0 +1,157 @@
/*
* Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* 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.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "config.h" /* must be first for large file support */
#include "FileInputPlugin.hxx"
#include "../InputStream.hxx"
#include "../InputPlugin.hxx"
#include "util/Error.hxx"
#include "util/Domain.hxx"
#include "fs/Traits.hxx"
#include "system/fd_util.h"
#include "open.h"
#include <sys/stat.h>
#include <unistd.h>
#include <errno.h>
static constexpr Domain file_domain("file");
struct FileInputStream {
InputStream base;
int fd;
FileInputStream(const char *path, int _fd, off_t size,
Mutex &mutex, Cond &cond)
:base(input_plugin_file, path, mutex, cond),
fd(_fd) {
base.size = size;
base.seekable = true;
base.ready = true;
}
~FileInputStream() {
close(fd);
}
};
static InputStream *
input_file_open(const char *filename,
Mutex &mutex, Cond &cond,
Error &error)
{
int fd, ret;
struct stat st;
if (!PathTraitsFS::IsAbsolute(filename))
return nullptr;
fd = open_cloexec(filename, O_RDONLY|O_BINARY, 0);
if (fd < 0) {
if (errno != ENOENT && errno != ENOTDIR)
error.FormatErrno("Failed to open \"%s\"",
filename);
return nullptr;
}
ret = fstat(fd, &st);
if (ret < 0) {
error.FormatErrno("Failed to stat \"%s\"", filename);
close(fd);
return nullptr;
}
if (!S_ISREG(st.st_mode)) {
error.Format(file_domain, "Not a regular file: %s", filename);
close(fd);
return nullptr;
}
#ifdef POSIX_FADV_SEQUENTIAL
posix_fadvise(fd, (off_t)0, st.st_size, POSIX_FADV_SEQUENTIAL);
#endif
FileInputStream *fis = new FileInputStream(filename, fd, st.st_size,
mutex, cond);
return &fis->base;
}
static bool
input_file_seek(InputStream *is, InputPlugin::offset_type offset,
int whence,
Error &error)
{
FileInputStream *fis = (FileInputStream *)is;
offset = (InputPlugin::offset_type)lseek(fis->fd, (off_t)offset, whence);
if (offset < 0) {
error.SetErrno("Failed to seek");
return false;
}
is->offset = offset;
return true;
}
static size_t
input_file_read(InputStream *is, void *ptr, size_t size,
Error &error)
{
FileInputStream *fis = (FileInputStream *)is;
ssize_t nbytes;
nbytes = read(fis->fd, ptr, size);
if (nbytes < 0) {
error.SetErrno("Failed to read");
return 0;
}
is->offset += nbytes;
return (size_t)nbytes;
}
static void
input_file_close(InputStream *is)
{
FileInputStream *fis = (FileInputStream *)is;
delete fis;
}
static bool
input_file_eof(InputStream *is)
{
return is->offset >= is->size;
}
const InputPlugin input_plugin_file = {
"file",
nullptr,
nullptr,
input_file_open,
input_file_close,
nullptr,
nullptr,
nullptr,
nullptr,
input_file_read,
input_file_eof,
input_file_seek,
};

View File

@@ -0,0 +1,25 @@
/*
* Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* 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.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifndef MPD_INPUT_FILE_HXX
#define MPD_INPUT_FILE_HXX
extern const struct InputPlugin input_plugin_file;
#endif

View File

@@ -0,0 +1,127 @@
/*
* Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* 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.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "config.h"
#include "MmsInputPlugin.hxx"
#include "../InputStream.hxx"
#include "../InputPlugin.hxx"
#include "util/StringUtil.hxx"
#include "util/Error.hxx"
#include "util/Domain.hxx"
#include <libmms/mmsx.h>
struct MmsInputStream {
InputStream base;
mmsx_t *mms;
bool eof;
MmsInputStream(const char *uri,
Mutex &mutex, Cond &cond,
mmsx_t *_mms)
:base(input_plugin_mms, uri, mutex, cond),
mms(_mms), eof(false) {
/* XX is this correct? at least this selects the ffmpeg
decoder, which seems to work fine*/
base.mime = "audio/x-ms-wma";
base.ready = true;
}
~MmsInputStream() {
mmsx_close(mms);
}
};
static constexpr Domain mms_domain("mms");
static InputStream *
input_mms_open(const char *url,
Mutex &mutex, Cond &cond,
Error &error)
{
if (!StringStartsWith(url, "mms://") &&
!StringStartsWith(url, "mmsh://") &&
!StringStartsWith(url, "mmst://") &&
!StringStartsWith(url, "mmsu://"))
return nullptr;
const auto mms = mmsx_connect(nullptr, nullptr, url, 128 * 1024);
if (mms == nullptr) {
error.Set(mms_domain, "mmsx_connect() failed");
return nullptr;
}
auto m = new MmsInputStream(url, mutex, cond, mms);
return &m->base;
}
static size_t
input_mms_read(InputStream *is, void *ptr, size_t size,
Error &error)
{
MmsInputStream *m = (MmsInputStream *)is;
int ret;
ret = mmsx_read(nullptr, m->mms, (char *)ptr, size);
if (ret <= 0) {
if (ret < 0)
error.SetErrno("mmsx_read() failed");
m->eof = true;
return false;
}
is->offset += ret;
return (size_t)ret;
}
static void
input_mms_close(InputStream *is)
{
MmsInputStream *m = (MmsInputStream *)is;
delete m;
}
static bool
input_mms_eof(InputStream *is)
{
MmsInputStream *m = (MmsInputStream *)is;
return m->eof;
}
const InputPlugin input_plugin_mms = {
"mms",
nullptr,
nullptr,
input_mms_open,
input_mms_close,
nullptr,
nullptr,
nullptr,
nullptr,
input_mms_read,
input_mms_eof,
nullptr,
};

View File

@@ -0,0 +1,25 @@
/*
* Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* 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.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifndef INPUT_MMS_H
#define INPUT_MMS_H
extern const struct InputPlugin input_plugin_mms;
#endif

View File

@@ -0,0 +1,251 @@
/*
* Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* 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.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "config.h"
#include "RewindInputPlugin.hxx"
#include "../InputStream.hxx"
#include "../InputPlugin.hxx"
#include <assert.h>
#include <string.h>
#include <stdio.h>
extern const InputPlugin rewind_input_plugin;
struct RewindInputStream {
InputStream base;
InputStream *input;
/**
* The read position within the buffer. Undefined as long as
* ReadingFromBuffer() returns false.
*/
size_t head;
/**
* The write/append position within the buffer.
*/
size_t tail;
/**
* The size of this buffer is the maximum number of bytes
* which can be rewinded cheaply without passing the "seek"
* call to CURL.
*
* The origin of this buffer is always the beginning of the
* stream (offset 0).
*/
char buffer[64 * 1024];
RewindInputStream(InputStream *_input)
:base(rewind_input_plugin, _input->uri.c_str(),
_input->mutex, _input->cond),
input(_input), tail(0) {
}
~RewindInputStream() {
input->Close();
}
/**
* Are we currently reading from the buffer, and does the
* buffer contain more data for the next read operation?
*/
bool ReadingFromBuffer() const {
return tail > 0 && base.offset < input->offset;
}
/**
* Copy public attributes from the underlying input stream to the
* "rewind" input stream. This function is called when a method of
* the underlying stream has returned, which may have modified these
* attributes.
*/
void CopyAttributes() {
InputStream *dest = &base;
const InputStream *src = input;
assert(dest != src);
bool dest_ready = dest->ready;
dest->ready = src->ready;
dest->seekable = src->seekable;
dest->size = src->size;
dest->offset = src->offset;
if (!dest_ready && src->ready)
dest->mime = src->mime;
}
};
static void
input_rewind_close(InputStream *is)
{
RewindInputStream *r = (RewindInputStream *)is;
delete r;
}
static bool
input_rewind_check(InputStream *is, Error &error)
{
RewindInputStream *r = (RewindInputStream *)is;
return r->input->Check(error);
}
static void
input_rewind_update(InputStream *is)
{
RewindInputStream *r = (RewindInputStream *)is;
if (!r->ReadingFromBuffer())
r->CopyAttributes();
}
static Tag *
input_rewind_tag(InputStream *is)
{
RewindInputStream *r = (RewindInputStream *)is;
return r->input->ReadTag();
}
static bool
input_rewind_available(InputStream *is)
{
RewindInputStream *r = (RewindInputStream *)is;
return r->input->IsAvailable();
}
static size_t
input_rewind_read(InputStream *is, void *ptr, size_t size,
Error &error)
{
RewindInputStream *r = (RewindInputStream *)is;
if (r->ReadingFromBuffer()) {
/* buffered read */
assert(r->head == (size_t)is->offset);
assert(r->tail == (size_t)r->input->offset);
if (size > r->tail - r->head)
size = r->tail - r->head;
memcpy(ptr, r->buffer + r->head, size);
r->head += size;
is->offset += size;
return size;
} else {
/* pass method call to underlying stream */
size_t nbytes = r->input->Read(ptr, size, error);
if (r->input->offset > (InputPlugin::offset_type)sizeof(r->buffer))
/* disable buffering */
r->tail = 0;
else if (r->tail == (size_t)is->offset) {
/* append to buffer */
memcpy(r->buffer + r->tail, ptr, nbytes);
r->tail += nbytes;
assert(r->tail == (size_t)r->input->offset);
}
r->CopyAttributes();
return nbytes;
}
}
static bool
input_rewind_eof(InputStream *is)
{
RewindInputStream *r = (RewindInputStream *)is;
return !r->ReadingFromBuffer() && r->input->IsEOF();
}
static bool
input_rewind_seek(InputStream *is, InputPlugin::offset_type offset,
int whence,
Error &error)
{
RewindInputStream *r = (RewindInputStream *)is;
assert(is->ready);
if (whence == SEEK_SET && r->tail > 0 &&
offset <= (InputPlugin::offset_type)r->tail) {
/* buffered seek */
assert(!r->ReadingFromBuffer() ||
r->head == (size_t)is->offset);
assert(r->tail == (size_t)r->input->offset);
r->head = (size_t)offset;
is->offset = offset;
return true;
} else {
bool success = r->input->Seek(offset, whence, error);
r->CopyAttributes();
/* disable the buffer, because r->input has left the
buffered range now */
r->tail = 0;
return success;
}
}
const InputPlugin rewind_input_plugin = {
nullptr,
nullptr,
nullptr,
nullptr,
input_rewind_close,
input_rewind_check,
input_rewind_update,
input_rewind_tag,
input_rewind_available,
input_rewind_read,
input_rewind_eof,
input_rewind_seek,
};
InputStream *
input_rewind_open(InputStream *is)
{
assert(is != nullptr);
assert(is->offset == 0);
if (is->seekable)
/* seekable resources don't need this plugin */
return is;
RewindInputStream *c = new RewindInputStream(is);
return &c->base;
}

View File

@@ -0,0 +1,37 @@
/*
* Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* 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.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
/** \file
*
* A wrapper for an input_stream object which allows cheap buffered
* rewinding. This is useful while detecting the stream codec (let
* each decoder plugin peek a portion from the stream).
*/
#ifndef MPD_INPUT_REWIND_HXX
#define MPD_INPUT_REWIND_HXX
#include "check.h"
struct InputStream;
InputStream *
input_rewind_open(InputStream *is);
#endif

View File

@@ -0,0 +1,203 @@
/*
* Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* 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.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "config.h"
#include "SmbclientInputPlugin.hxx"
#include "../InputStream.hxx"
#include "../InputPlugin.hxx"
#include "util/StringUtil.hxx"
#include "util/Error.hxx"
#include <libsmbclient.h>
#include <string.h>
class SmbclientInputStream {
InputStream base;
SMBCCTX *ctx;
int fd;
public:
SmbclientInputStream(const char *uri,
Mutex &mutex, Cond &cond,
SMBCCTX *_ctx, int _fd, const struct stat &st)
:base(input_plugin_smbclient, uri, mutex, cond),
ctx(_ctx), fd(_fd) {
base.ready = true;
base.seekable = true;
base.size = st.st_size;
}
~SmbclientInputStream() {
smbc_close(fd);
smbc_free_context(ctx, 1);
}
InputStream *GetBase() {
return &base;
}
bool IsEOF() const {
return base.offset >= base.size;
}
size_t Read(void *ptr, size_t size, Error &error) {
ssize_t nbytes = smbc_read(fd, ptr, size);
if (nbytes < 0) {
error.SetErrno("smbc_read() failed");
nbytes = 0;
}
return nbytes;
}
bool Seek(InputStream::offset_type offset, int whence, Error &error) {
off_t result = smbc_lseek(fd, offset, whence);
if (result < 0) {
error.SetErrno("smbc_lseek() failed");
return false;
}
base.offset = result;
return true;
}
};
static void
mpd_smbc_get_auth_data(gcc_unused const char *srv,
gcc_unused const char *shr,
char *wg, gcc_unused int wglen,
char *un, gcc_unused int unlen,
char *pw, gcc_unused int pwlen)
{
// TODO: implement
strcpy(wg, "WORKGROUP");
strcpy(un, "foo");
strcpy(pw, "bar");
}
/*
* InputPlugin methods
*
*/
static bool
input_smbclient_init(gcc_unused const config_param &param, Error &error)
{
constexpr int debug = 0;
if (smbc_init(mpd_smbc_get_auth_data, debug) < 0) {
error.SetErrno("smbc_init() failed");
return false;
}
// TODO: create one global SMBCCTX here?
// TODO: evaluate config_param, call smbc_setOption*()
return true;
}
static InputStream *
input_smbclient_open(const char *uri,
Mutex &mutex, Cond &cond,
Error &error)
{
if (!StringStartsWith(uri, "smb://"))
return nullptr;
SMBCCTX *ctx = smbc_new_context();
if (ctx == nullptr) {
error.SetErrno("smbc_new_context() failed");
return nullptr;
}
SMBCCTX *ctx2 = smbc_init_context(ctx);
if (ctx2 == nullptr) {
error.SetErrno("smbc_init_context() failed");
smbc_free_context(ctx, 1);
return nullptr;
}
ctx = ctx2;
int fd = smbc_open(uri, O_RDONLY, 0);
if (fd < 0) {
error.SetErrno("smbc_open() failed");
smbc_free_context(ctx, 1);
return nullptr;
}
struct stat st;
if (smbc_fstat(fd, &st) < 0) {
error.SetErrno("smbc_fstat() failed");
smbc_close(fd);
smbc_free_context(ctx, 1);
return nullptr;
}
auto s = new SmbclientInputStream(uri, mutex, cond, ctx, fd, st);
return s->GetBase();
}
static size_t
input_smbclient_read(InputStream *is, void *ptr, size_t size,
Error &error)
{
SmbclientInputStream &s = *(SmbclientInputStream *)is;
return s.Read(ptr, size, error);
}
static void
input_smbclient_close(InputStream *is)
{
SmbclientInputStream *s = (SmbclientInputStream *)is;
delete s;
}
static bool
input_smbclient_eof(InputStream *is)
{
SmbclientInputStream &s = *(SmbclientInputStream *)is;
return s.IsEOF();
}
static bool
input_smbclient_seek(InputStream *is,
InputPlugin::offset_type offset, int whence,
Error &error)
{
SmbclientInputStream &s = *(SmbclientInputStream *)is;
return s.Seek(offset, whence, error);
}
const InputPlugin input_plugin_smbclient = {
"smbclient",
input_smbclient_init,
nullptr,
input_smbclient_open,
input_smbclient_close,
nullptr,
nullptr,
nullptr,
nullptr,
input_smbclient_read,
input_smbclient_eof,
input_smbclient_seek,
};

View File

@@ -0,0 +1,25 @@
/*
* Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* 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.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifndef MPD_INPUT_SMBCLIENT_H
#define MPD_INPUT_SMBCLIENT_H
extern const struct InputPlugin input_plugin_smbclient;
#endif