shout: use the new encoder API
Removed shout's encoder plugin API in favor of the new generic encoder plugin API.
This commit is contained in:
parent
f7c685f1ab
commit
f6e5c00726
@ -557,8 +557,7 @@ if test x$enable_shout_ogg = xyes; then
|
|||||||
enable_shout_ogg=no
|
enable_shout_ogg=no
|
||||||
fi
|
fi
|
||||||
if test x$enable_shout_ogg = xyes; then
|
if test x$enable_shout_ogg = xyes; then
|
||||||
PKG_CHECK_MODULES(VORBISENC, [vorbisenc],
|
PKG_CHECK_MODULES(VORBISENC, [vorbisenc],,
|
||||||
AC_DEFINE(HAVE_SHOUT_OGG, 1, [Define to enable ogg streaming support]),
|
|
||||||
enable_shout_ogg=no)
|
enable_shout_ogg=no)
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
@ -573,9 +572,6 @@ if test x$enable_shout_mp3 = xyes; then
|
|||||||
AC_MSG_WARN([disabling mp3 shout streaming support because lame is not enabled])
|
AC_MSG_WARN([disabling mp3 shout streaming support because lame is not enabled])
|
||||||
enable_shout_mp3=no
|
enable_shout_mp3=no
|
||||||
fi
|
fi
|
||||||
if test x$enable_shout_mp3 = xyes; then
|
|
||||||
AC_DEFINE(HAVE_SHOUT_MP3, 1, [Define to enable mp3 streaming support])
|
|
||||||
fi
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if test x$enable_shout_ogg = xyes || test x$enable_shout_mp3 = xyes; then
|
if test x$enable_shout_ogg = xyes || test x$enable_shout_mp3 = xyes; then
|
||||||
@ -586,8 +582,6 @@ else
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
AM_CONDITIONAL(HAVE_SHOUT, test x$enable_shout = xyes)
|
AM_CONDITIONAL(HAVE_SHOUT, test x$enable_shout = xyes)
|
||||||
AM_CONDITIONAL(HAVE_SHOUT_OGG, test x$enable_shout_ogg = xyes)
|
|
||||||
AM_CONDITIONAL(HAVE_SHOUT_MP3, test x$enable_shout_mp3 = xyes)
|
|
||||||
|
|
||||||
AM_CONDITIONAL(ENABLE_ENCODER, test x$enable_shout = xyes)
|
AM_CONDITIONAL(ENABLE_ENCODER, test x$enable_shout = xyes)
|
||||||
AM_CONDITIONAL(ENABLE_VORBIS_ENCODER, test x$enable_shout_ogg = xyes)
|
AM_CONDITIONAL(ENABLE_VORBIS_ENCODER, test x$enable_shout_ogg = xyes)
|
||||||
|
@ -419,14 +419,6 @@ if HAVE_SHOUT
|
|||||||
mpd_SOURCES += output/shout_plugin.c
|
mpd_SOURCES += output/shout_plugin.c
|
||||||
endif
|
endif
|
||||||
|
|
||||||
if HAVE_SHOUT_MP3
|
|
||||||
mpd_SOURCES += output/shout_mp3.c
|
|
||||||
endif
|
|
||||||
|
|
||||||
if HAVE_SHOUT_OGG
|
|
||||||
mpd_SOURCES += output/shout_ogg.c
|
|
||||||
endif
|
|
||||||
|
|
||||||
# sparse is a semantic parser
|
# sparse is a semantic parser
|
||||||
# URL: git://www.kernel.org/pub/scm/devel/sparse/sparse.git
|
# URL: git://www.kernel.org/pub/scm/devel/sparse/sparse.git
|
||||||
SPARSE = sparse
|
SPARSE = sparse
|
||||||
|
@ -1,193 +0,0 @@
|
|||||||
/* the Music Player Daemon (MPD)
|
|
||||||
* Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com)
|
|
||||||
* This project's homepage is: 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "shout_plugin.h"
|
|
||||||
|
|
||||||
#include <lame/lame.h>
|
|
||||||
|
|
||||||
#include <assert.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
|
|
||||||
struct lame_data {
|
|
||||||
lame_global_flags *gfp;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
static int shout_mp3_encoder_init(struct shout_data *sd)
|
|
||||||
{
|
|
||||||
struct lame_data *ld = g_new(struct lame_data, 1);
|
|
||||||
|
|
||||||
sd->encoder_data = ld;
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int shout_mp3_encoder_clear_encoder(struct shout_data *sd)
|
|
||||||
{
|
|
||||||
struct lame_data *ld = (struct lame_data *)sd->encoder_data;
|
|
||||||
struct shout_buffer *buf = &sd->buf;
|
|
||||||
int ret;
|
|
||||||
|
|
||||||
ret = lame_encode_flush(ld->gfp, buf->data, sizeof(buf->data));
|
|
||||||
if (ret < 0)
|
|
||||||
g_warning("error flushing lame buffers\n");
|
|
||||||
|
|
||||||
lame_close(ld->gfp);
|
|
||||||
ld->gfp = NULL;
|
|
||||||
|
|
||||||
return (ret > 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void shout_mp3_encoder_finish(struct shout_data *sd)
|
|
||||||
{
|
|
||||||
struct lame_data *ld = (struct lame_data *)sd->encoder_data;
|
|
||||||
|
|
||||||
assert(ld->gfp == NULL);
|
|
||||||
|
|
||||||
g_free(ld);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int shout_mp3_encoder_init_encoder(struct shout_data *sd)
|
|
||||||
{
|
|
||||||
struct lame_data *ld = (struct lame_data *)sd->encoder_data;
|
|
||||||
|
|
||||||
if (NULL == (ld->gfp = lame_init())) {
|
|
||||||
g_warning("error initializing lame encoder for shout\n");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sd->quality >= -1.0) {
|
|
||||||
if (0 != lame_set_VBR(ld->gfp, vbr_rh)) {
|
|
||||||
g_warning("error setting lame VBR mode\n");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
if (0 != lame_set_VBR_q(ld->gfp, sd->quality)) {
|
|
||||||
g_warning("error setting lame VBR quality\n");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (0 != lame_set_brate(ld->gfp, sd->bitrate)) {
|
|
||||||
g_warning("error setting lame bitrate\n");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (0 != lame_set_num_channels(ld->gfp,
|
|
||||||
sd->audio_format.channels)) {
|
|
||||||
g_warning("error setting lame num channels\n");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (0 != lame_set_in_samplerate(ld->gfp,
|
|
||||||
sd->audio_format.sample_rate)) {
|
|
||||||
g_warning("error setting lame sample rate\n");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (0 > lame_init_params(ld->gfp))
|
|
||||||
g_error("error initializing lame params\n");
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int shout_mp3_encoder_send_metadata(struct shout_data *sd,
|
|
||||||
char * song, size_t size)
|
|
||||||
{
|
|
||||||
char artist[size];
|
|
||||||
char title[size];
|
|
||||||
int i;
|
|
||||||
const struct tag *tag = sd->tag;
|
|
||||||
|
|
||||||
strncpy(artist, "", size);
|
|
||||||
strncpy(title, "", size);
|
|
||||||
|
|
||||||
for (i = 0; i < tag->numOfItems; i++) {
|
|
||||||
switch (tag->items[i]->type) {
|
|
||||||
case TAG_ITEM_ARTIST:
|
|
||||||
strncpy(artist, tag->items[i]->value, size);
|
|
||||||
break;
|
|
||||||
case TAG_ITEM_TITLE:
|
|
||||||
strncpy(title, tag->items[i]->value, size);
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
snprintf(song, size, "%s - %s", title, artist);
|
|
||||||
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
shout_mp3_encoder_encode(struct shout_data *sd, const void *chunk, size_t len)
|
|
||||||
{
|
|
||||||
const int16_t *src = (const int16_t*)chunk;
|
|
||||||
unsigned int i;
|
|
||||||
float *left, *right;
|
|
||||||
struct shout_buffer *buf = &(sd->buf);
|
|
||||||
unsigned int samples;
|
|
||||||
struct lame_data *ld = (struct lame_data *)sd->encoder_data;
|
|
||||||
int bytes_out;
|
|
||||||
|
|
||||||
samples = len / audio_format_sample_size(&sd->audio_format);
|
|
||||||
left = g_malloc(sizeof(left[0]) * samples);
|
|
||||||
if (sd->audio_format.channels > 1)
|
|
||||||
right = g_malloc(sizeof(left[0]) * samples);
|
|
||||||
else
|
|
||||||
right = left;
|
|
||||||
|
|
||||||
/* this is for only 16-bit audio */
|
|
||||||
|
|
||||||
for (i = 0; i < samples; i++) {
|
|
||||||
left[i] = src[0];
|
|
||||||
if (right != left)
|
|
||||||
right[i] = src[1];
|
|
||||||
src += sd->audio_format.channels;
|
|
||||||
}
|
|
||||||
|
|
||||||
bytes_out = lame_encode_buffer_float(ld->gfp, left, right,
|
|
||||||
samples, buf->data,
|
|
||||||
sizeof(buf->data));
|
|
||||||
|
|
||||||
g_free(left);
|
|
||||||
if (right != left)
|
|
||||||
g_free(right);
|
|
||||||
|
|
||||||
if (0 > bytes_out) {
|
|
||||||
g_warning("error encoding lame buffer for shout\n");
|
|
||||||
lame_close(ld->gfp);
|
|
||||||
ld->gfp = NULL;
|
|
||||||
return -1;
|
|
||||||
} else
|
|
||||||
buf->len = bytes_out; /* signed to unsigned conversion */
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
const struct shout_encoder_plugin shout_mp3_encoder = {
|
|
||||||
"mp3",
|
|
||||||
SHOUT_FORMAT_MP3,
|
|
||||||
|
|
||||||
shout_mp3_encoder_clear_encoder,
|
|
||||||
shout_mp3_encoder_encode,
|
|
||||||
shout_mp3_encoder_finish,
|
|
||||||
shout_mp3_encoder_init,
|
|
||||||
shout_mp3_encoder_init_encoder,
|
|
||||||
shout_mp3_encoder_send_metadata,
|
|
||||||
};
|
|
@ -1,293 +0,0 @@
|
|||||||
/* the Music Player Daemon (MPD)
|
|
||||||
* Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com)
|
|
||||||
* This project's homepage is: 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "shout_plugin.h"
|
|
||||||
|
|
||||||
#include <vorbis/vorbisenc.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
|
|
||||||
struct ogg_vorbis_data {
|
|
||||||
ogg_stream_state os;
|
|
||||||
ogg_page og;
|
|
||||||
ogg_packet op;
|
|
||||||
ogg_packet header_main;
|
|
||||||
ogg_packet header_comments;
|
|
||||||
ogg_packet header_codebooks;
|
|
||||||
|
|
||||||
vorbis_dsp_state vd;
|
|
||||||
vorbis_block vb;
|
|
||||||
vorbis_info vi;
|
|
||||||
vorbis_comment vc;
|
|
||||||
};
|
|
||||||
|
|
||||||
static void add_tag(struct ogg_vorbis_data *od, const char *name, char *value)
|
|
||||||
{
|
|
||||||
if (value) {
|
|
||||||
union {
|
|
||||||
const char *in;
|
|
||||||
char *out;
|
|
||||||
} u = { .in = name };
|
|
||||||
vorbis_comment_add_tag(&od->vc, u.out, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void copy_tag_to_vorbis_comment(struct shout_data *sd)
|
|
||||||
{
|
|
||||||
struct ogg_vorbis_data *od = (struct ogg_vorbis_data *)sd->encoder_data;
|
|
||||||
|
|
||||||
if (sd->tag) {
|
|
||||||
int i;
|
|
||||||
|
|
||||||
for (i = 0; i < sd->tag->numOfItems; i++) {
|
|
||||||
switch (sd->tag->items[i]->type) {
|
|
||||||
case TAG_ITEM_ARTIST:
|
|
||||||
add_tag(od, "ARTIST", sd->tag->items[i]->value);
|
|
||||||
break;
|
|
||||||
case TAG_ITEM_ALBUM:
|
|
||||||
add_tag(od, "ALBUM", sd->tag->items[i]->value);
|
|
||||||
break;
|
|
||||||
case TAG_ITEM_TITLE:
|
|
||||||
add_tag(od, "TITLE", sd->tag->items[i]->value);
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static int copy_ogg_buffer_to_shout_buffer(ogg_page *og,
|
|
||||||
struct shout_buffer *buf)
|
|
||||||
{
|
|
||||||
if ((size_t)og->header_len + (size_t)og->body_len > sizeof(buf->data)) {
|
|
||||||
g_warning("%s: not enough buffer space!\n", __func__);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
memcpy(buf->data, og->header, og->header_len);
|
|
||||||
memcpy(buf->data + og->header_len, og->body, og->body_len);
|
|
||||||
buf->len = og->header_len + og->body_len;
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int flush_ogg_buffer(struct shout_data *sd)
|
|
||||||
{
|
|
||||||
struct shout_buffer *buf = &sd->buf;
|
|
||||||
struct ogg_vorbis_data *od = (struct ogg_vorbis_data *)sd->encoder_data;
|
|
||||||
int ret = 0;
|
|
||||||
|
|
||||||
if (ogg_stream_flush(&od->os, &od->og))
|
|
||||||
ret = copy_ogg_buffer_to_shout_buffer(&od->og, buf);
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int send_ogg_vorbis_header(struct shout_data *sd)
|
|
||||||
{
|
|
||||||
struct ogg_vorbis_data *od = (struct ogg_vorbis_data *)sd->encoder_data;
|
|
||||||
|
|
||||||
vorbis_analysis_headerout(&od->vd, &od->vc,
|
|
||||||
&od->header_main,
|
|
||||||
&od->header_comments,
|
|
||||||
&od->header_codebooks);
|
|
||||||
|
|
||||||
ogg_stream_packetin(&od->os, &od->header_main);
|
|
||||||
ogg_stream_packetin(&od->os, &od->header_comments);
|
|
||||||
ogg_stream_packetin(&od->os, &od->header_codebooks);
|
|
||||||
|
|
||||||
return flush_ogg_buffer(sd);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void finish_encoder(struct ogg_vorbis_data *od)
|
|
||||||
{
|
|
||||||
vorbis_analysis_wrote(&od->vd, 0);
|
|
||||||
|
|
||||||
while (vorbis_analysis_blockout(&od->vd, &od->vb) == 1) {
|
|
||||||
vorbis_analysis(&od->vb, NULL);
|
|
||||||
vorbis_bitrate_addblock(&od->vb);
|
|
||||||
while (vorbis_bitrate_flushpacket(&od->vd, &od->op)) {
|
|
||||||
ogg_stream_packetin(&od->os, &od->op);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static int shout_ogg_encoder_clear_encoder(struct shout_data *sd)
|
|
||||||
{
|
|
||||||
struct ogg_vorbis_data *od = (struct ogg_vorbis_data *)sd->encoder_data;
|
|
||||||
int ret;
|
|
||||||
|
|
||||||
finish_encoder(od);
|
|
||||||
if ((ret = ogg_stream_pageout(&od->os, &od->og)))
|
|
||||||
copy_ogg_buffer_to_shout_buffer(&od->og, &sd->buf);
|
|
||||||
|
|
||||||
vorbis_comment_clear(&od->vc);
|
|
||||||
ogg_stream_clear(&od->os);
|
|
||||||
vorbis_block_clear(&od->vb);
|
|
||||||
vorbis_dsp_clear(&od->vd);
|
|
||||||
vorbis_info_clear(&od->vi);
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void shout_ogg_encoder_finish(struct shout_data *sd)
|
|
||||||
{
|
|
||||||
struct ogg_vorbis_data *od = (struct ogg_vorbis_data *)sd->encoder_data;
|
|
||||||
|
|
||||||
if (od) {
|
|
||||||
free(od);
|
|
||||||
sd->encoder_data = NULL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static int shout_ogg_encoder_init(struct shout_data *sd)
|
|
||||||
{
|
|
||||||
struct ogg_vorbis_data *od = g_new(struct ogg_vorbis_data, 1);
|
|
||||||
|
|
||||||
sd->encoder_data = od;
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int reinit_encoder(struct shout_data *sd)
|
|
||||||
{
|
|
||||||
struct ogg_vorbis_data *od = (struct ogg_vorbis_data *)sd->encoder_data;
|
|
||||||
|
|
||||||
vorbis_info_init(&od->vi);
|
|
||||||
|
|
||||||
if (sd->quality >= -1.0) {
|
|
||||||
if (0 != vorbis_encode_init_vbr(&od->vi,
|
|
||||||
sd->audio_format.channels,
|
|
||||||
sd->audio_format.sample_rate,
|
|
||||||
sd->quality * 0.1)) {
|
|
||||||
g_warning("error initializing vorbis vbr\n");
|
|
||||||
vorbis_info_clear(&od->vi);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (0 != vorbis_encode_init(&od->vi,
|
|
||||||
sd->audio_format.channels,
|
|
||||||
sd->audio_format.sample_rate, -1.0,
|
|
||||||
sd->bitrate * 1000, -1.0)) {
|
|
||||||
g_warning("error initializing vorbis encoder\n");
|
|
||||||
vorbis_info_clear(&od->vi);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
vorbis_analysis_init(&od->vd, &od->vi);
|
|
||||||
vorbis_block_init(&od->vd, &od->vb);
|
|
||||||
ogg_stream_init(&od->os, rand());
|
|
||||||
vorbis_comment_init(&od->vc);
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int shout_ogg_encoder_init_encoder(struct shout_data *sd)
|
|
||||||
{
|
|
||||||
if (reinit_encoder(sd))
|
|
||||||
return -1;
|
|
||||||
|
|
||||||
if (send_ogg_vorbis_header(sd)) {
|
|
||||||
g_warning("error sending ogg vorbis header for shout\n");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int shout_ogg_encoder_send_metadata(struct shout_data *sd,
|
|
||||||
G_GNUC_UNUSED char * song,
|
|
||||||
G_GNUC_UNUSED size_t size)
|
|
||||||
{
|
|
||||||
struct ogg_vorbis_data *od = (struct ogg_vorbis_data *)sd->encoder_data;
|
|
||||||
|
|
||||||
shout_ogg_encoder_clear_encoder(sd);
|
|
||||||
if (reinit_encoder(sd))
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
copy_tag_to_vorbis_comment(sd);
|
|
||||||
|
|
||||||
vorbis_analysis_headerout(&od->vd, &od->vc,
|
|
||||||
&od->header_main,
|
|
||||||
&od->header_comments,
|
|
||||||
&od->header_codebooks);
|
|
||||||
|
|
||||||
ogg_stream_packetin(&od->os, &od->header_main);
|
|
||||||
ogg_stream_packetin(&od->os, &od->header_comments);
|
|
||||||
ogg_stream_packetin(&od->os, &od->header_codebooks);
|
|
||||||
|
|
||||||
flush_ogg_buffer(sd);
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
pcm16_to_ogg_buffer(float **dest, const int16_t *src,
|
|
||||||
unsigned num_samples, unsigned num_channels)
|
|
||||||
{
|
|
||||||
for (unsigned i = 0; i < num_samples; i++)
|
|
||||||
for (unsigned j = 0; j < num_channels; j++)
|
|
||||||
dest[j][i] = *src++ / 32768.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
shout_ogg_encoder_encode(struct shout_data *sd,
|
|
||||||
const void *chunk, size_t size)
|
|
||||||
{
|
|
||||||
struct shout_buffer *buf = &sd->buf;
|
|
||||||
unsigned int samples;
|
|
||||||
struct ogg_vorbis_data *od = (struct ogg_vorbis_data *)sd->encoder_data;
|
|
||||||
|
|
||||||
samples = size / audio_format_frame_size(&sd->audio_format);
|
|
||||||
|
|
||||||
/* this is for only 16-bit audio */
|
|
||||||
|
|
||||||
pcm16_to_ogg_buffer(vorbis_analysis_buffer(&od->vd, samples),
|
|
||||||
(const int16_t *)chunk,
|
|
||||||
samples, sd->audio_format.channels);
|
|
||||||
|
|
||||||
vorbis_analysis_wrote(&od->vd, samples);
|
|
||||||
|
|
||||||
while (1 == vorbis_analysis_blockout(&od->vd, &od->vb)) {
|
|
||||||
vorbis_analysis(&od->vb, NULL);
|
|
||||||
vorbis_bitrate_addblock(&od->vb);
|
|
||||||
|
|
||||||
while (vorbis_bitrate_flushpacket(&od->vd, &od->op)) {
|
|
||||||
ogg_stream_packetin(&od->os, &od->op);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ogg_stream_pageout(&od->os, &od->og))
|
|
||||||
copy_ogg_buffer_to_shout_buffer(&od->og, buf);
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
const struct shout_encoder_plugin shout_ogg_encoder = {
|
|
||||||
"ogg",
|
|
||||||
SHOUT_FORMAT_VORBIS,
|
|
||||||
|
|
||||||
shout_ogg_encoder_clear_encoder,
|
|
||||||
shout_ogg_encoder_encode,
|
|
||||||
shout_ogg_encoder_finish,
|
|
||||||
shout_ogg_encoder_init,
|
|
||||||
shout_ogg_encoder_init_encoder,
|
|
||||||
shout_ogg_encoder_send_metadata,
|
|
||||||
};
|
|
@ -17,6 +17,8 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "shout_plugin.h"
|
#include "shout_plugin.h"
|
||||||
|
#include "encoder_plugin.h"
|
||||||
|
#include "encoder_list.h"
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
@ -37,16 +39,15 @@ static const struct shout_encoder_plugin *const shout_encoder_plugins[] = {
|
|||||||
NULL
|
NULL
|
||||||
};
|
};
|
||||||
|
|
||||||
static const struct shout_encoder_plugin *
|
static const struct encoder_plugin *
|
||||||
shout_encoder_plugin_get(const char *name)
|
shout_encoder_plugin_get(const char *name)
|
||||||
{
|
{
|
||||||
unsigned i;
|
if (strcmp(name, "ogg") == 0)
|
||||||
|
name = "vorbis";
|
||||||
|
else if (strcmp(name, "mp3") == 0)
|
||||||
|
name = "lame";
|
||||||
|
|
||||||
for (i = 0; shout_encoder_plugins[i] != NULL; ++i)
|
return encoder_plugin_get(name);
|
||||||
if (strcmp(shout_encoder_plugins[i]->name, name) == 0)
|
|
||||||
return shout_encoder_plugins[i];
|
|
||||||
|
|
||||||
return NULL;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static struct shout_data *new_shout_data(void)
|
static struct shout_data *new_shout_data(void)
|
||||||
@ -91,6 +92,9 @@ static void *my_shout_init_driver(struct audio_output *audio_output,
|
|||||||
char *mount;
|
char *mount;
|
||||||
char *passwd;
|
char *passwd;
|
||||||
const char *encoding;
|
const char *encoding;
|
||||||
|
const struct encoder_plugin *encoder_plugin;
|
||||||
|
GError *error = NULL;
|
||||||
|
unsigned shout_format;
|
||||||
unsigned protocol;
|
unsigned protocol;
|
||||||
const char *user;
|
const char *user;
|
||||||
char *name;
|
char *name;
|
||||||
@ -162,15 +166,21 @@ static void *my_shout_init_driver(struct audio_output *audio_output,
|
|||||||
|
|
||||||
check_block_param("format");
|
check_block_param("format");
|
||||||
|
|
||||||
assert(audio_format != NULL);
|
|
||||||
sd->audio_format = *audio_format;
|
|
||||||
|
|
||||||
encoding = config_get_block_string(param, "encoding", "ogg");
|
encoding = config_get_block_string(param, "encoding", "ogg");
|
||||||
sd->encoder = shout_encoder_plugin_get(encoding);
|
encoder_plugin = shout_encoder_plugin_get(encoding);
|
||||||
if (sd->encoder == NULL)
|
if (encoder_plugin == NULL)
|
||||||
g_error("couldn't find shout encoder plugin \"%s\"\n",
|
g_error("couldn't find shout encoder plugin \"%s\"\n",
|
||||||
encoding);
|
encoding);
|
||||||
|
|
||||||
|
sd->encoder = encoder_init(encoder_plugin, param, &error);
|
||||||
|
if (sd->encoder == NULL)
|
||||||
|
g_error("%s", error->message);
|
||||||
|
|
||||||
|
if (strcmp(encoding, "mp3") == 0 || strcmp(encoding, "lame") == 0)
|
||||||
|
shout_format = SHOUT_FORMAT_MP3;
|
||||||
|
else
|
||||||
|
shout_format = SHOUT_FORMAT_OGG;
|
||||||
|
|
||||||
value = config_get_block_string(param, "protocol", NULL);
|
value = config_get_block_string(param, "protocol", NULL);
|
||||||
if (value != NULL) {
|
if (value != NULL) {
|
||||||
if (0 == strcmp(value, "shoutcast") &&
|
if (0 == strcmp(value, "shoutcast") &&
|
||||||
@ -199,7 +209,7 @@ static void *my_shout_init_driver(struct audio_output *audio_output,
|
|||||||
shout_set_name(sd->shout_conn, name) != SHOUTERR_SUCCESS ||
|
shout_set_name(sd->shout_conn, name) != SHOUTERR_SUCCESS ||
|
||||||
shout_set_user(sd->shout_conn, user) != SHOUTERR_SUCCESS ||
|
shout_set_user(sd->shout_conn, user) != SHOUTERR_SUCCESS ||
|
||||||
shout_set_public(sd->shout_conn, public) != SHOUTERR_SUCCESS ||
|
shout_set_public(sd->shout_conn, public) != SHOUTERR_SUCCESS ||
|
||||||
shout_set_format(sd->shout_conn, sd->encoder->shout_format)
|
shout_set_format(sd->shout_conn, shout_format)
|
||||||
!= SHOUTERR_SUCCESS ||
|
!= SHOUTERR_SUCCESS ||
|
||||||
shout_set_protocol(sd->shout_conn, protocol) != SHOUTERR_SUCCESS ||
|
shout_set_protocol(sd->shout_conn, protocol) != SHOUTERR_SUCCESS ||
|
||||||
shout_set_agent(sd->shout_conn, "MPD") != SHOUTERR_SUCCESS) {
|
shout_set_agent(sd->shout_conn, "MPD") != SHOUTERR_SUCCESS) {
|
||||||
@ -227,10 +237,10 @@ static void *my_shout_init_driver(struct audio_output *audio_output,
|
|||||||
char temp[11];
|
char temp[11];
|
||||||
memset(temp, 0, sizeof(temp));
|
memset(temp, 0, sizeof(temp));
|
||||||
|
|
||||||
snprintf(temp, sizeof(temp), "%u", sd->audio_format.channels);
|
snprintf(temp, sizeof(temp), "%u", audio_format->channels);
|
||||||
shout_set_audio_info(sd->shout_conn, SHOUT_AI_CHANNELS, temp);
|
shout_set_audio_info(sd->shout_conn, SHOUT_AI_CHANNELS, temp);
|
||||||
|
|
||||||
snprintf(temp, sizeof(temp), "%u", sd->audio_format.sample_rate);
|
snprintf(temp, sizeof(temp), "%u", audio_format->sample_rate);
|
||||||
|
|
||||||
shout_set_audio_info(sd->shout_conn, SHOUT_AI_SAMPLERATE, temp);
|
shout_set_audio_info(sd->shout_conn, SHOUT_AI_SAMPLERATE, temp);
|
||||||
|
|
||||||
@ -245,10 +255,6 @@ static void *my_shout_init_driver(struct audio_output *audio_output,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sd->encoder->init_func(sd) != 0)
|
|
||||||
g_error("shout: encoder plugin '%s' failed to initialize\n",
|
|
||||||
sd->encoder->name);
|
|
||||||
|
|
||||||
return sd;
|
return sd;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -283,6 +289,10 @@ write_page(struct shout_data *sd)
|
|||||||
{
|
{
|
||||||
int err;
|
int err;
|
||||||
|
|
||||||
|
assert(sd->encoder != NULL);
|
||||||
|
|
||||||
|
sd->buf.len = encoder_read(sd->encoder,
|
||||||
|
sd->buf.data, sizeof(sd->buf.data));
|
||||||
if (sd->buf.len == 0)
|
if (sd->buf.len == 0)
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
@ -298,8 +308,12 @@ static void close_shout_conn(struct shout_data * sd)
|
|||||||
{
|
{
|
||||||
sd->buf.len = 0;
|
sd->buf.len = 0;
|
||||||
|
|
||||||
if (sd->encoder->clear_encoder_func(sd))
|
if (sd->encoder != NULL) {
|
||||||
write_page(sd);
|
if (encoder_flush(sd->encoder, NULL))
|
||||||
|
write_page(sd);
|
||||||
|
|
||||||
|
encoder_close(sd->encoder);
|
||||||
|
}
|
||||||
|
|
||||||
if (shout_get_connected(sd->shout_conn) != SHOUTERR_UNCONNECTED &&
|
if (shout_get_connected(sd->shout_conn) != SHOUTERR_UNCONNECTED &&
|
||||||
shout_close(sd->shout_conn) != SHOUTERR_SUCCESS) {
|
shout_close(sd->shout_conn) != SHOUTERR_SUCCESS) {
|
||||||
@ -312,7 +326,8 @@ static void my_shout_finish_driver(void *data)
|
|||||||
{
|
{
|
||||||
struct shout_data *sd = (struct shout_data *)data;
|
struct shout_data *sd = (struct shout_data *)data;
|
||||||
|
|
||||||
sd->encoder->finish_func(sd);
|
encoder_finish(sd->encoder);
|
||||||
|
|
||||||
free_shout_data(sd);
|
free_shout_data(sd);
|
||||||
|
|
||||||
shout_init_count--;
|
shout_init_count--;
|
||||||
@ -357,11 +372,11 @@ shout_connect(struct shout_data *sd)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
my_shout_open_device(void *data,
|
my_shout_open_device(void *data, struct audio_format *audio_format)
|
||||||
G_GNUC_UNUSED struct audio_format *audio_format)
|
|
||||||
{
|
{
|
||||||
struct shout_data *sd = (struct shout_data *)data;
|
struct shout_data *sd = (struct shout_data *)data;
|
||||||
bool ret;
|
bool ret;
|
||||||
|
GError *error = NULL;
|
||||||
|
|
||||||
ret = shout_connect(sd);
|
ret = shout_connect(sd);
|
||||||
if (!ret)
|
if (!ret)
|
||||||
@ -369,8 +384,11 @@ my_shout_open_device(void *data,
|
|||||||
|
|
||||||
sd->buf.len = 0;
|
sd->buf.len = 0;
|
||||||
|
|
||||||
if (sd->encoder->init_encoder_func(sd) < 0) {
|
ret = encoder_open(sd->encoder, audio_format, &error);
|
||||||
|
if (!ret) {
|
||||||
shout_close(sd->shout_conn);
|
shout_close(sd->shout_conn);
|
||||||
|
g_warning("%s", error->message);
|
||||||
|
g_error_free(error);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -383,11 +401,15 @@ static bool
|
|||||||
my_shout_play(void *data, const char *chunk, size_t size)
|
my_shout_play(void *data, const char *chunk, size_t size)
|
||||||
{
|
{
|
||||||
struct shout_data *sd = (struct shout_data *)data;
|
struct shout_data *sd = (struct shout_data *)data;
|
||||||
|
bool ret;
|
||||||
|
GError *error = NULL;
|
||||||
|
|
||||||
sd->buf.len = 0;
|
ret = encoder_write(sd->encoder, chunk, size, &error);
|
||||||
|
if (!ret) {
|
||||||
if (sd->encoder->encode_func(sd, chunk, size))
|
g_warning("%s", error->message);
|
||||||
|
g_error_free(error);
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
return write_page(sd);
|
return write_page(sd);
|
||||||
}
|
}
|
||||||
@ -400,17 +422,63 @@ my_shout_pause(void *data)
|
|||||||
return my_shout_play(data, silence, sizeof(silence));
|
return my_shout_play(data, silence, sizeof(silence));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
shout_tag_to_metadata(const struct tag *tag, char *dest, size_t size)
|
||||||
|
{
|
||||||
|
char artist[size];
|
||||||
|
char title[size];
|
||||||
|
int i;
|
||||||
|
|
||||||
|
artist[0] = 0;
|
||||||
|
title[0] = 0;
|
||||||
|
|
||||||
|
for (i = 0; i < tag->numOfItems; i++) {
|
||||||
|
switch (tag->items[i]->type) {
|
||||||
|
case TAG_ITEM_ARTIST:
|
||||||
|
strncpy(artist, tag->items[i]->value, size);
|
||||||
|
break;
|
||||||
|
case TAG_ITEM_TITLE:
|
||||||
|
strncpy(title, tag->items[i]->value, size);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
snprintf(dest, size, "%s - %s", title, artist);
|
||||||
|
}
|
||||||
|
|
||||||
static void my_shout_set_tag(void *data,
|
static void my_shout_set_tag(void *data,
|
||||||
const struct tag *tag)
|
const struct tag *tag)
|
||||||
{
|
{
|
||||||
struct shout_data *sd = (struct shout_data *)data;
|
struct shout_data *sd = (struct shout_data *)data;
|
||||||
char song[1024];
|
|
||||||
bool ret;
|
bool ret;
|
||||||
|
GError *error = NULL;
|
||||||
|
|
||||||
|
if (sd->encoder->plugin->tag != NULL) {
|
||||||
|
/* encoder plugin supports stream tags */
|
||||||
|
|
||||||
|
ret = encoder_flush(sd->encoder, &error);
|
||||||
|
if (!ret) {
|
||||||
|
g_warning("%s", error->message);
|
||||||
|
g_error_free(error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
write_page(sd);
|
||||||
|
|
||||||
|
ret = encoder_tag(sd->encoder, tag, &error);
|
||||||
|
if (!ret) {
|
||||||
|
g_warning("%s", error->message);
|
||||||
|
g_error_free(error);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
/* no stream tag support: fall back to icy-metadata */
|
||||||
|
char song[1024];
|
||||||
|
|
||||||
|
shout_tag_to_metadata(tag, song, sizeof(song));
|
||||||
|
|
||||||
sd->buf.len = 0;
|
|
||||||
sd->tag = tag;
|
|
||||||
ret = sd->encoder->send_metadata_func(sd, song, sizeof(song));
|
|
||||||
if (ret) {
|
|
||||||
shout_metadata_add(sd->shout_meta, "song", song);
|
shout_metadata_add(sd->shout_meta, "song", song);
|
||||||
if (SHOUTERR_SUCCESS != shout_set_metadata(sd->shout_conn,
|
if (SHOUTERR_SUCCESS != shout_set_metadata(sd->shout_conn,
|
||||||
sd->shout_meta)) {
|
sd->shout_meta)) {
|
||||||
|
@ -28,28 +28,6 @@
|
|||||||
#undef G_LOG_DOMAIN
|
#undef G_LOG_DOMAIN
|
||||||
#define G_LOG_DOMAIN "shout"
|
#define G_LOG_DOMAIN "shout"
|
||||||
|
|
||||||
struct shout_data;
|
|
||||||
|
|
||||||
struct shout_encoder_plugin {
|
|
||||||
const char *name;
|
|
||||||
unsigned int shout_format;
|
|
||||||
|
|
||||||
int (*clear_encoder_func)(struct shout_data *sd);
|
|
||||||
int (*encode_func)(struct shout_data *sd,
|
|
||||||
const void *chunk, size_t len);
|
|
||||||
void (*finish_func)(struct shout_data *sd);
|
|
||||||
int (*init_func)(struct shout_data *sd);
|
|
||||||
int (*init_encoder_func) (struct shout_data *sd);
|
|
||||||
/* Called when there is a new MpdTag to encode into the
|
|
||||||
stream. If this function returns non-zero, then the
|
|
||||||
resulting song will be passed to the shout server as
|
|
||||||
metadata. This allows the Ogg encoder to send metadata via
|
|
||||||
Vorbis comments in the stream, while an MP3 encoder can use
|
|
||||||
the Shout Server's metadata API. */
|
|
||||||
int (*send_metadata_func)(struct shout_data *sd,
|
|
||||||
char *song, size_t size);
|
|
||||||
};
|
|
||||||
|
|
||||||
struct shout_buffer {
|
struct shout_buffer {
|
||||||
unsigned char data[32768];
|
unsigned char data[32768];
|
||||||
size_t len;
|
size_t len;
|
||||||
@ -61,23 +39,14 @@ struct shout_data {
|
|||||||
shout_t *shout_conn;
|
shout_t *shout_conn;
|
||||||
shout_metadata_t *shout_meta;
|
shout_metadata_t *shout_meta;
|
||||||
|
|
||||||
const struct shout_encoder_plugin *encoder;
|
struct encoder *encoder;
|
||||||
void *encoder_data;
|
|
||||||
|
|
||||||
float quality;
|
float quality;
|
||||||
int bitrate;
|
int bitrate;
|
||||||
|
|
||||||
const struct tag *tag;
|
|
||||||
|
|
||||||
int timeout;
|
int timeout;
|
||||||
|
|
||||||
/* the configured audio format */
|
|
||||||
struct audio_format audio_format;
|
|
||||||
|
|
||||||
struct shout_buffer buf;
|
struct shout_buffer buf;
|
||||||
};
|
};
|
||||||
|
|
||||||
extern const struct shout_encoder_plugin shout_mp3_encoder;
|
|
||||||
extern const struct shout_encoder_plugin shout_ogg_encoder;
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
Loading…
Reference in New Issue
Block a user