Input*: move to input/
This commit is contained in:
431
src/input/plugins/AlsaInputPlugin.cxx
Normal file
431
src/input/plugins/AlsaInputPlugin.cxx
Normal 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,
|
||||
};
|
||||
28
src/input/plugins/AlsaInputPlugin.hxx
Normal file
28
src/input/plugins/AlsaInputPlugin.hxx
Normal 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
|
||||
99
src/input/plugins/ArchiveInputPlugin.cxx
Normal file
99
src/input/plugins/ArchiveInputPlugin.cxx
Normal 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,
|
||||
};
|
||||
25
src/input/plugins/ArchiveInputPlugin.hxx
Normal file
25
src/input/plugins/ArchiveInputPlugin.hxx
Normal 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
|
||||
407
src/input/plugins/CdioParanoiaInputPlugin.cxx
Normal file
407
src/input/plugins/CdioParanoiaInputPlugin.cxx
Normal 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 ¶m, 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,
|
||||
};
|
||||
28
src/input/plugins/CdioParanoiaInputPlugin.hxx
Normal file
28
src/input/plugins/CdioParanoiaInputPlugin.hxx
Normal 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
|
||||
1170
src/input/plugins/CurlInputPlugin.cxx
Normal file
1170
src/input/plugins/CurlInputPlugin.cxx
Normal file
File diff suppressed because it is too large
Load Diff
25
src/input/plugins/CurlInputPlugin.hxx
Normal file
25
src/input/plugins/CurlInputPlugin.hxx
Normal 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
|
||||
262
src/input/plugins/DespotifyInputPlugin.cxx
Normal file
262
src/input/plugins/DespotifyInputPlugin.cxx
Normal 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,
|
||||
};
|
||||
25
src/input/plugins/DespotifyInputPlugin.hxx
Normal file
25
src/input/plugins/DespotifyInputPlugin.hxx
Normal 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
|
||||
177
src/input/plugins/FfmpegInputPlugin.cxx
Normal file
177
src/input/plugins/FfmpegInputPlugin.cxx
Normal 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 ¶m,
|
||||
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,
|
||||
};
|
||||
28
src/input/plugins/FfmpegInputPlugin.hxx
Normal file
28
src/input/plugins/FfmpegInputPlugin.hxx
Normal 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
|
||||
157
src/input/plugins/FileInputPlugin.cxx
Normal file
157
src/input/plugins/FileInputPlugin.cxx
Normal 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,
|
||||
};
|
||||
25
src/input/plugins/FileInputPlugin.hxx
Normal file
25
src/input/plugins/FileInputPlugin.hxx
Normal 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
|
||||
127
src/input/plugins/MmsInputPlugin.cxx
Normal file
127
src/input/plugins/MmsInputPlugin.cxx
Normal 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,
|
||||
};
|
||||
25
src/input/plugins/MmsInputPlugin.hxx
Normal file
25
src/input/plugins/MmsInputPlugin.hxx
Normal 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
|
||||
251
src/input/plugins/RewindInputPlugin.cxx
Normal file
251
src/input/plugins/RewindInputPlugin.cxx
Normal 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;
|
||||
}
|
||||
37
src/input/plugins/RewindInputPlugin.hxx
Normal file
37
src/input/plugins/RewindInputPlugin.hxx
Normal 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
|
||||
203
src/input/plugins/SmbclientInputPlugin.cxx
Normal file
203
src/input/plugins/SmbclientInputPlugin.cxx
Normal 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 ¶m, 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,
|
||||
};
|
||||
25
src/input/plugins/SmbclientInputPlugin.hxx
Normal file
25
src/input/plugins/SmbclientInputPlugin.hxx
Normal 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
|
||||
Reference in New Issue
Block a user