Support for live ICY metadata for streams (incl. artwork)

This commit is contained in:
ejurgensen 2015-03-14 21:42:53 +01:00
parent 34d815a130
commit 6221e24f1b
12 changed files with 709 additions and 108 deletions

View File

@ -114,6 +114,7 @@ forked_daapd_SOURCES = main.c \
transcode.c transcode.h \
pipe.c pipe.h \
artwork.c artwork.h \
icy.h icy.c \
misc.c misc.h \
rng.c rng.h \
rsp_query.c rsp_query.h \

View File

@ -38,6 +38,7 @@
#include "logger.h"
#include "conffile.h"
#include "cache.h"
#include "player.h"
#if LIBAVFORMAT_VERSION_MAJOR >= 53
# include "avio_evbuffer.h"
@ -725,6 +726,127 @@ artwork_get(char *path, int max_w, int max_h, struct evbuffer *evbuf)
return ret;
}
static int
artwork_get_player_image(char *path, int max_w, int max_h, struct evbuffer *evbuf)
{
AVFormatContext *src_ctx;
AVPacket pkt;
char *url;
int len;
int s;
int target_w;
int target_h;
int format_ok;
int ret;
DPRINTF(E_DBG, L_ART, "Trying internet stream artwork in %s\n", path);
player_icy_artwork_url(&url, path);
if (!url)
return 0;
len = strlen(url);
if ((len < 14) || (len > PATH_MAX)) // Can't be shorter than http://a/1.jpg
return 0;
src_ctx = NULL;
ret = avformat_open_input(&src_ctx, url, NULL, NULL);
if (ret < 0)
{
DPRINTF(E_WARN, L_ART, "Cannot open artwork file '%s': %s\n", url, strerror(AVUNERROR(ret)));
return 0;
}
free(url);
format_ok = 0;
for (s = 0; s < src_ctx->nb_streams; s++)
{
if (src_ctx->streams[s]->codec->codec_id == AV_CODEC_ID_PNG)
{
format_ok = ART_FMT_PNG;
break;
}
else if (src_ctx->streams[s]->codec->codec_id == AV_CODEC_ID_MJPEG)
{
format_ok = ART_FMT_JPEG;
break;
}
}
if (s == src_ctx->nb_streams)
{
DPRINTF(E_LOG, L_ART, "Artwork file '%s' not a PNG or JPEG file\n", path);
avformat_close_input(&src_ctx);
return 0;
}
ret = 0;
while (av_read_frame(src_ctx, &pkt) == 0)
{
if (pkt.stream_index != s)
{
av_free_packet(&pkt);
continue;
}
ret = 1;
break;
}
if (!ret)
{
DPRINTF(E_LOG, L_ART, "Could not read artwork: '%s'\n", path);
avformat_close_input(&src_ctx);
return 0;
}
ret = rescale_needed(src_ctx->streams[s]->codec, max_w, max_h, &target_w, &target_h);
/* Fastpath */
if (!ret && format_ok)
{
DPRINTF(E_DBG, L_ART, "Artwork not too large, using original image\n");
ret = evbuffer_expand(evbuf, pkt.size);
if (ret < 0)
{
DPRINTF(E_LOG, L_ART, "Out of memory for artwork\n");
av_free_packet(&pkt);
avformat_close_input(&src_ctx);
return -1;
}
ret = evbuffer_add(evbuf, pkt.data, pkt.size);
if (ret < 0)
{
DPRINTF(E_LOG, L_ART, "Could not add image to event buffer\n");
}
else
ret = format_ok;
}
else
{
DPRINTF(E_DBG, L_ART, "Artwork too large, rescaling image\n");
ret = artwork_rescale(src_ctx, s, target_w, target_h, evbuf);
}
av_free_packet(&pkt);
avformat_close_input(&src_ctx);
if (ret < 0)
{
if (evbuffer_get_length(evbuf) > 0)
evbuffer_drain(evbuf, evbuffer_get_length(evbuf));
}
return ret;
}
#if LIBAVFORMAT_VERSION_MAJOR >= 55 || (LIBAVFORMAT_VERSION_MAJOR == 54 && LIBAVFORMAT_VERSION_MINOR >= 6)
static int
artwork_get_embedded_image(char *path, int max_w, int max_h, struct evbuffer *evbuf)
@ -786,7 +908,7 @@ artwork_get_embedded_image(char *path, int max_w, int max_h, struct evbuffer *ev
if (s == src_ctx->nb_streams)
{
DPRINTF(E_SPAM, L_ART, "Did not find embedded artwork in '%s'\n", path);
DPRINTF(E_DBG, L_ART, "Did not find embedded artwork in '%s'\n", path);
avformat_close_input(&src_ctx);
return -1;
@ -1062,6 +1184,9 @@ artwork_get_item_path(char *in_path, int artwork, int max_w, int max_h, char *ou
ret = artwork_get_embedded_image(in_path, max_w, max_h, evbuf);
break;
#endif
case ARTWORK_HTTP:
ret = artwork_get_player_image(in_path, max_w, max_h, evbuf);
break;
}
return ret;

View File

@ -62,6 +62,11 @@ struct db_unlock {
pthread_mutex_t lck;
};
struct async_query {
char *query;
int delay;
};
#define DB_TYPE_CHAR 1
#define DB_TYPE_INT 2
#define DB_TYPE_INT64 3
@ -620,7 +625,7 @@ db_exec(const char *query, char **errmsg)
static void *
db_exec_thread(void *arg)
{
char *query = arg;
struct async_query *async = arg;
char *errmsg;
time_t start, end;
int ret;
@ -628,7 +633,7 @@ db_exec_thread(void *arg)
// When switching tracks we update playcount and select the next track's
// metadata. We want the update to run after the selects so it won't lock
// the database.
sleep(3);
sleep(async->delay);
ret = db_perthread_init();
if (ret < 0)
@ -637,19 +642,20 @@ db_exec_thread(void *arg)
return NULL;
}
DPRINTF(E_DBG, L_DB, "Running delayed query '%s'\n", query);
DPRINTF(E_DBG, L_DB, "Running delayed query '%s'\n", async->query);
time(&start);
ret = db_exec(query, &errmsg);
ret = db_exec(async->query, &errmsg);
if (ret != SQLITE_OK)
DPRINTF(E_LOG, L_DB, "Error running query '%s': %s\n", query, errmsg);
DPRINTF(E_LOG, L_DB, "Error running query '%s': %s\n", async->query, errmsg);
time(&end);
if (end - start > 1)
DPRINTF(E_LOG, L_DB, "Warning: Slow query detected '%s' - database performance problems?\n", query);
DPRINTF(E_LOG, L_DB, "Warning: Slow query detected '%s' - database performance problems?\n", async->query);
sqlite3_free(errmsg);
sqlite3_free(query);
sqlite3_free(async->query);
free(async);
db_perthread_deinit();
@ -658,8 +664,9 @@ db_exec_thread(void *arg)
// Creates a one-off thread to run a delayed, fire-and-forget, non-blocking query
static void
db_exec_nonblock(char *query)
db_exec_nonblock(char *query, int delay)
{
struct async_query *async;
pthread_t tid;
pthread_attr_t attr;
int ret;
@ -671,8 +678,17 @@ db_exec_nonblock(char *query)
return;
}
async = malloc(sizeof(struct async_query));
if (!async)
{
DPRINTF(E_LOG, L_DB, "Out of memory\n");
return;
}
async->query = query;
async->delay = delay;
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
ret = pthread_create(&tid, &attr, db_exec_thread, query);
ret = pthread_create(&tid, &attr, db_exec_thread, async);
if (ret != 0)
{
DPRINTF(E_LOG, L_DB, "Error in db_exec_nonblock: Could not create thread\n");
@ -2020,7 +2036,7 @@ db_file_inc_playcount(int id)
}
// Run the query non-blocking so we don't block playback if the update is slow
db_exec_nonblock(query);
db_exec_nonblock(query, 5);
#undef Q_TMPL
}
@ -2666,6 +2682,27 @@ db_file_update(struct media_file_info *mfi)
#undef Q_TMPL
}
void
db_file_update_icy(int id, char *artist, char *album)
{
#define Q_TMPL "UPDATE files SET artist = TRIM(%Q), album = TRIM(%Q) WHERE id = %d;"
char *query;
if (id == 0)
return;
query = sqlite3_mprintf(Q_TMPL, artist, album, id);
if (!query)
{
DPRINTF(E_LOG, L_DB, "Out of memory for query string\n");
return;
}
db_exec_nonblock(query, 0);
#undef Q_TMPL
}
void
db_file_delete_bypath(char *path)
{

View File

@ -49,6 +49,7 @@ enum query_type {
#define ARTWORK_DIR 4
#define ARTWORK_PARENTDIR 5
#define ARTWORK_SPOTIFY 6
#define ARTWORK_HTTP 7
enum filelistitem_type {
F_PLAYLIST = 1,
@ -425,6 +426,9 @@ db_file_add(struct media_file_info *mfi);
int
db_file_update(struct media_file_info *mfi);
void
db_file_update_icy(int id, char *artist, char *album);
void
db_file_delete_bypath(char *path);

View File

@ -37,6 +37,7 @@
#include "logger.h"
#include "filescanner.h"
#include "misc.h"
#include "icy.h"
/* Legacy format-specific scanners */
@ -314,88 +315,13 @@ extract_metadata(struct media_file_info *mfi, AVFormatContext *ctx, AVStream *au
return mdcount;
}
#if LIBAVFORMAT_VERSION_MAJOR >= 56 || (LIBAVFORMAT_VERSION_MAJOR == 55 && LIBAVFORMAT_VERSION_MINOR >= 13)
/* Extracts ICY metadata (requires libav 10) */
static void
extract_metadata_icy(struct media_file_info *mfi, AVFormatContext *ctx)
{
uint8_t *icy_meta;
char *icy_token;
char *icy_str;
char *ptr;
icy_meta = NULL;
// TODO Also get icy_metadata_packet to show current track
av_opt_get(ctx, "icy_metadata_headers", AV_OPT_SEARCH_CHILDREN, &icy_meta);
if (!icy_meta)
return;
icy_str = strdup((char *)icy_meta);
icy_token = strtok(icy_str, "\r\n");
while (icy_token != NULL)
{
ptr = strchr(icy_token, ':');
if (!ptr || (strlen(ptr) < 4))
{
icy_token = strtok(NULL, "\r\n");
continue;
}
ptr++;
if (ptr[0] == ' ')
ptr++;
if (strstr(icy_token, "icy-name"))
{
DPRINTF(E_DBG, L_SCAN, "Libav/ffmpeg found ICY metadata, name is '%s'\n", ptr);
if (mfi->title)
free(mfi->title);
if (mfi->artist)
free(mfi->artist);
if (mfi->album_artist)
free(mfi->album_artist);
mfi->title = strdup(ptr);
mfi->artist = strdup(ptr);
mfi->album_artist = strdup(ptr);
}
if (strstr(icy_token, "icy-description"))
{
DPRINTF(E_DBG, L_SCAN, "Libav/ffmpeg found ICY metadata, description is '%s'\n", ptr);
if (mfi->album)
free(mfi->album);
mfi->album = strdup(ptr);
}
if (strstr(icy_token, "icy-genre"))
{
DPRINTF(E_DBG, L_SCAN, "Libav/ffmpeg found ICY metadata, genre is '%s'\n", ptr);
if (mfi->genre)
free(mfi->genre);
mfi->genre = strdup(ptr);
}
icy_token = strtok(NULL, "\r\n");
}
av_free(icy_meta);
free(icy_str);
}
#endif
int
scan_metadata_ffmpeg(char *file, struct media_file_info *mfi)
{
AVFormatContext *ctx;
AVDictionary *options;
const struct metadata_map *extra_md_map;
struct icy_metadata *icy_metadata;
#if LIBAVCODEC_VERSION_MAJOR >= 55 || (LIBAVCODEC_VERSION_MAJOR == 54 && LIBAVCODEC_VERSION_MINOR >= 35)
enum AVCodecID codec_id;
enum AVCodecID video_codec_id;
@ -425,7 +351,10 @@ scan_metadata_ffmpeg(char *file, struct media_file_info *mfi)
# endif
if (mfi->data_kind == 1)
av_dict_set(&options, "icy", "1", 0);
{
av_dict_set(&options, "icy", "1", 0);
mfi->artwork = ARTWORK_HTTP;
}
ret = avformat_open_input(&ctx, file, NULL, &options);
#else
@ -553,11 +482,46 @@ scan_metadata_ffmpeg(char *file, struct media_file_info *mfi)
DPRINTF(E_DBG, L_SCAN, "Duration %d ms, bitrate %d kbps\n", mfi->song_length, mfi->bitrate);
#if LIBAVFORMAT_VERSION_MAJOR >= 56 || (LIBAVFORMAT_VERSION_MAJOR == 55 && LIBAVFORMAT_VERSION_MINOR >= 13)
/* Try to extract ICY metadata if url/stream */
if (mfi->data_kind == 1)
extract_metadata_icy(mfi, ctx);
#endif
{
icy_metadata = icy_metadata_get(ctx, 0);
if (icy_metadata && icy_metadata->name)
{
DPRINTF(E_DBG, L_SCAN, "libav/ffmpeg found ICY metadata, name is '%s'\n", icy_metadata->name);
if (mfi->title)
free(mfi->title);
if (mfi->artist)
free(mfi->artist);
if (mfi->album_artist)
free(mfi->album_artist);
mfi->title = strdup(icy_metadata->name);
mfi->artist = strdup(icy_metadata->name);
mfi->album_artist = strdup(icy_metadata->name);
}
if (icy_metadata && icy_metadata->description)
{
DPRINTF(E_DBG, L_SCAN, "libav/ffmpeg found ICY metadata, description is '%s'\n", icy_metadata->description);
if (mfi->album)
free(mfi->album);
mfi->album = strdup(icy_metadata->description);
}
if (icy_metadata && icy_metadata->genre)
{
DPRINTF(E_DBG, L_SCAN, "libav/ffmpeg found ICY metadata, genre is '%s'\n", icy_metadata->genre);
if (mfi->genre)
free(mfi->genre);
mfi->genre = strdup(icy_metadata->genre);
}
if (icy_metadata)
icy_metadata_free(icy_metadata);
}
/* Get some more information on the audio stream */
if (audio_stream)

217
src/icy.c Normal file
View File

@ -0,0 +1,217 @@
/*
* Copyright (C) 2015 Espen Jürgensen <espenjurgensen@gmail.com>
*
* 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
*/
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <limits.h>
#include <sys/param.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include "icy.h"
#include "logger.h"
#include "misc.h"
#include <libavutil/opt.h>
static int
metadata_packet_get(struct icy_metadata *metadata, AVFormatContext *fmtctx)
{
uint8_t *buffer;
char *icy_token;
char *ptr;
char *end;
av_opt_get(fmtctx, "icy_metadata_packet", AV_OPT_SEARCH_CHILDREN, &buffer);
if (!buffer)
return -1;
icy_token = strtok((char *)buffer, ";");
while (icy_token != NULL)
{
ptr = strchr(icy_token, '=');
if (!ptr || (ptr[1] == '\0'))
{
icy_token = strtok(NULL, ";");
continue;
}
ptr++;
if (ptr[0] == '\'')
ptr++;
end = strrchr(ptr, '\'');
if (end)
*end = '\0';
if (strncmp(icy_token, "StreamTitle", strlen("StreamTitle")) == 0)
{
metadata->title = ptr;
/* Dash separates artist from title, if no dash assume all is title */
ptr = strstr(ptr, " - ");
if (ptr)
{
*ptr = '\0';
metadata->title = strdup(metadata->title);
*ptr = ' ';
metadata->artist = strdup(ptr + 3);
}
else
metadata->title = strdup(metadata->title);
}
else if (strncmp(icy_token, "StreamUrl", strlen("StreamUrl")) == 0)
metadata->artwork_url = strdup(ptr);
if (end)
*end = '\'';
icy_token = strtok(NULL, ";");
}
av_free(buffer);
if (metadata->title)
metadata->hash = djb_hash(metadata->title, strlen(metadata->title));
return 0;
}
static int
metadata_header_get(struct icy_metadata *metadata, AVFormatContext *fmtctx)
{
uint8_t *buffer;
char *icy_token;
char *ptr;
av_opt_get(fmtctx, "icy_metadata_headers", AV_OPT_SEARCH_CHILDREN, &buffer);
if (!buffer)
return -1;
icy_token = strtok((char *)buffer, "\r\n");
while (icy_token != NULL)
{
ptr = strchr(icy_token, ':');
if (!ptr || (ptr[1] == '\0'))
{
icy_token = strtok(NULL, "\r\n");
continue;
}
ptr++;
if (ptr[0] == ' ')
ptr++;
if (strncmp(icy_token, "icy-name", strlen("icy-name")) == 0)
metadata->name = strdup(ptr);
else if (strncmp(icy_token, "icy-description", strlen("icy-description")) == 0)
metadata->description = strdup(ptr);
else if (strncmp(icy_token, "icy-genre", strlen("icy-genre")) == 0)
metadata->genre = strdup(ptr);
icy_token = strtok(NULL, "\r\n");
}
av_free(buffer);
return 0;
}
void
icy_metadata_free(struct icy_metadata *metadata)
{
if (metadata->name)
free(metadata->name);
if (metadata->description)
free(metadata->description);
if (metadata->genre)
free(metadata->genre);
if (metadata->title)
free(metadata->title);
if (metadata->artist)
free(metadata->artist);
if (metadata->artwork_url)
free(metadata->artwork_url);
free(metadata);
}
#if LIBAVFORMAT_VERSION_MAJOR >= 56 || (LIBAVFORMAT_VERSION_MAJOR == 55 && LIBAVFORMAT_VERSION_MINOR >= 13)
/* Extracts ICY header and packet metadata (requires libav 10)
*
* example header metadata (standard http header format):
* icy-name: Rock On Radio
* example packet metadata (track currently being played):
* StreamTitle='Robert Miles - Black Rubber';StreamUrl='';
*
* The extraction is straight from the stream and done in the player thread, so
* it must not produce significant delay.
*/
struct icy_metadata *
icy_metadata_get(AVFormatContext *fmtctx, int packet_only)
{
struct icy_metadata *metadata;
int got_packet;
int got_header;
metadata = malloc(sizeof(struct icy_metadata));
if (!metadata)
return NULL;
memset(metadata, 0, sizeof(struct icy_metadata));
got_packet = (metadata_packet_get(metadata, fmtctx) == 0);
got_header = (!packet_only) && (metadata_header_get(metadata, fmtctx) == 0);
if (!got_packet && !got_header)
{
free(metadata);
return NULL;
}
/* DPRINTF(E_DBG, L_MISC, "Found ICY: N %s, D %s, G %s, T %s, A %s, U %s, I %" PRIu32 "\n",
metadata->name,
metadata->description,
metadata->genre,
metadata->title,
metadata->artist,
metadata->artwork_url,
metadata->hash
);
*/
return metadata;
}
#else
struct icy_metadata *
icy_metadata_get(AVFormatContext *fmtctx, int packet_only)
{
return NULL;
}
#endif

28
src/icy.h Normal file
View File

@ -0,0 +1,28 @@
#ifndef __ICY_H__
#define __ICY_H__
#include <libavformat/avformat.h>
struct icy_metadata
{
/* Static stream metadata from icy_metadata_headers */
char *name;
char *description;
char *genre;
/* Track specific, comes from icy_metadata_packet */
char *title;
char *artist;
char *artwork_url;
uint32_t hash;
};
void
icy_metadata_free(struct icy_metadata *metadata);
struct icy_metadata *
icy_metadata_get(AVFormatContext *fmtctx, int packet_only);
#endif /* !__ICY_H__ */

View File

@ -65,6 +65,7 @@
/* These handle getting the media data */
#include "transcode.h"
#include "pipe.h"
#include "icy.h"
#ifdef HAVE_SPOTIFY_H
# include "spotify.h"
#endif
@ -77,6 +78,9 @@
#define MAX(a, b) ((a > b) ? a : b)
#endif
/* Interval between ICY metadata polls for streams, in seconds */
#define METADATA_ICY_POLL 5
enum player_sync_source
{
PLAYER_SYNC_CLOCK,
@ -128,6 +132,12 @@ struct item_range
uint32_t *id_ptr;
};
struct icy_artwork
{
char *stream_url;
char *artwork_url;
};
struct player_command
{
pthread_mutex_t lck;
@ -153,6 +163,7 @@ struct player_command
int intval;
int ps_pos[2];
struct item_range item_range;
struct icy_artwork icy_artwork;
} arg;
int ret;
@ -180,6 +191,7 @@ static int cmd_pipe[2];
static int player_exit;
static struct event *exitev;
static struct event *cmdev;
static struct event *metaev;
static pthread_t tid_player;
/* Player status */
@ -661,6 +673,59 @@ metadata_send(struct player_source *ps, int startup)
raop_metadata_send(ps->id, rtptime, offset, startup);
}
static void
metadata_icy_poll_cb(int fd, short what, void *arg)
{
struct timeval tv = { METADATA_ICY_POLL, 0 };
struct icy_metadata *metadata;
int changed;
/* Playback of stream has stopped, so stop polling */
if (!cur_streaming || cur_streaming->type != SOURCE_HTTP || !cur_streaming->ctx)
{
if (metaev)
event_free(metaev);
metaev = NULL;
return;
}
transcode_metadata(cur_streaming->ctx, &metadata, &changed);
if (!metadata)
goto no_metadata;
if (!changed)
goto no_update;
/* Update db (async) and send status update to clients */
db_file_update_icy(cur_streaming->id, metadata->artist, metadata->title);
status_update(player_state);
metadata_send(cur_streaming, 0);
no_update:
icy_metadata_free(metadata);
no_metadata:
evtimer_add(metaev, &tv);
}
static void
metadata_icy_poll_start(void)
{
struct timeval tv = { METADATA_ICY_POLL, 0 };
DPRINTF(E_DBG, L_PLAYER, "Starting ICY polling\n");
if (metaev)
return;
metaev = evtimer_new(evbase_player, metadata_icy_poll_cb, NULL);
if (!metaev)
return;
evtimer_add(metaev, &tv);
}
/* Audio sources */
/* Thread: httpd (DACP) */
static struct player_source *
@ -1059,7 +1124,8 @@ source_free(struct player_source *ps)
{
switch (ps->type)
{
case SOURCE_FFMPEG:
case SOURCE_FILE:
case SOURCE_HTTP:
if (ps->ctx)
transcode_cleanup(ps->ctx);
break;
@ -1087,7 +1153,8 @@ source_stop(struct player_source *ps)
{
switch (ps->type)
{
case SOURCE_FFMPEG:
case SOURCE_FILE:
case SOURCE_HTTP:
if (ps->ctx)
{
transcode_cleanup(ps->ctx);
@ -1274,6 +1341,13 @@ source_open(struct player_source *ps, int no_md)
// Setup the source type responsible for getting the audio
switch (mfi->data_kind)
{
case 1:
ps->type = SOURCE_HTTP;
ret = transcode_setup(&ps->ctx, mfi, NULL, 0);
if (ret >= 0)
metadata_icy_poll_start();
break;
case 2:
ps->type = SOURCE_SPOTIFY;
#ifdef HAVE_SPOTIFY_H
@ -1289,7 +1363,7 @@ source_open(struct player_source *ps, int no_md)
break;
default:
ps->type = SOURCE_FFMPEG;
ps->type = SOURCE_FILE;
ret = transcode_setup(&ps->ctx, mfi, NULL, 0);
}
@ -1347,7 +1421,7 @@ source_next(int force)
if (!cur_streaming)
break;
if ((cur_streaming->type == SOURCE_FFMPEG) && cur_streaming->ctx)
if ((cur_streaming->type == SOURCE_FILE) && cur_streaming->ctx)
{
ret = transcode_seek(cur_streaming->ctx, 0);
@ -1585,7 +1659,7 @@ source_check(void)
if (ps->setup_done)
{
if ((ps->type == SOURCE_FFMPEG) && ps->ctx)
if ((ps->type == SOURCE_FILE) && ps->ctx)
{
transcode_cleanup(ps->ctx);
ps->ctx = NULL;
@ -1639,7 +1713,7 @@ source_check(void)
if (ps->setup_done)
{
if ((ps->type == SOURCE_FFMPEG) && ps->ctx)
if ((ps->type == SOURCE_FILE) && ps->ctx)
{
transcode_cleanup(ps->ctx);
ps->ctx = NULL;
@ -1726,7 +1800,8 @@ source_read(uint8_t *buf, int len, uint64_t rtptime)
{
switch (cur_streaming->type)
{
case SOURCE_FFMPEG:
case SOURCE_FILE:
case SOURCE_HTTP:
ret = transcode(cur_streaming->ctx, audio_buf, len - nbytes);
break;
@ -2457,6 +2532,22 @@ now_playing(struct player_command *cmd)
return 0;
}
static int
artwork_url_get(struct player_command *cmd)
{
DPRINTF(E_DBG, L_PLAYER, "ICY artwork url call\n");
cmd->arg.icy_artwork.artwork_url = NULL;
/* Not playing a stream */
if (!cur_playing || cur_playing->type != SOURCE_HTTP || !cur_playing->ctx)
return -1;
transcode_metadata_artwork_url(cur_playing->ctx, &cmd->arg.icy_artwork.artwork_url, cmd->arg.icy_artwork.stream_url);
return 0;
}
static int
playback_stop(struct player_command *cmd)
{
@ -2907,7 +2998,7 @@ playback_seek_bh(struct player_command *cmd)
/* Seek to commanded position */
switch (ps->type)
{
case SOURCE_FFMPEG:
case SOURCE_FILE:
ret = transcode_seek(ps->ctx, ms);
break;
#ifdef HAVE_SPOTIFY_H
@ -2916,8 +3007,10 @@ playback_seek_bh(struct player_command *cmd)
break;
#endif
case SOURCE_PIPE:
case SOURCE_HTTP:
ret = 1;
break;
default:
ret = -1;
}
@ -2964,7 +3057,7 @@ playback_pause_bh(struct player_command *cmd)
switch (ps->type)
{
case SOURCE_FFMPEG:
case SOURCE_FILE:
ret = transcode_seek(ps->ctx, ms);
break;
#ifdef HAVE_SPOTIFY_H
@ -4110,6 +4203,30 @@ player_now_playing(uint32_t *id)
return ret;
}
int
player_icy_artwork_url(char **artwork_url, char *stream_url)
{
struct player_command cmd;
int ret;
command_init(&cmd);
cmd.func = artwork_url_get;
cmd.func_bh = NULL;
cmd.arg.icy_artwork.stream_url = stream_url;
if (pthread_self() != tid_player)
ret = sync_command(&cmd);
else
ret = artwork_url_get(&cmd);
*artwork_url = cmd.arg.icy_artwork.artwork_url;
command_deinit(&cmd);
return ret;
}
/*
* Starts/resumes playback
*

View File

@ -32,9 +32,10 @@ enum repeat_mode {
};
enum source_type {
SOURCE_FFMPEG = 0,
SOURCE_FILE = 0,
SOURCE_SPOTIFY,
SOURCE_PIPE,
SOURCE_HTTP,
};
struct spk_flags {
@ -132,6 +133,9 @@ player_get_status(struct player_status *status);
int
player_now_playing(uint32_t *id);
int
player_icy_artwork_url(char **artwork_url, char *stream_url);
void
player_speaker_enumerate(spk_enum_cb cb, void *arg);

View File

@ -61,6 +61,7 @@
#include "evrtsp/evrtsp.h"
#include <gcrypt.h>
#include <pthread.h>
#include "conffile.h"
#include "logger.h"
@ -157,6 +158,14 @@ struct raop_metadata
struct raop_metadata *next;
};
struct raop_metadata_send_ctx
{
int id;
uint64_t rtptime;
uint64_t offset;
int startup;
};
struct raop_service
{
int fd;
@ -2162,17 +2171,25 @@ raop_metadata_startup_send(struct raop_session *rs)
}
}
void
raop_metadata_send(int id, uint64_t rtptime, uint64_t offset, int startup)
static void *
raop_metadata_send_thread(void *arg)
{
struct raop_metadata_send_ctx *ctx = arg;
struct raop_session *rs;
struct raop_metadata *rmd;
uint32_t delay;
int ret;
rmd = raop_metadata_prepare(id, rtptime);
ret = db_perthread_init();
if (ret < 0)
{
DPRINTF(E_LOG, L_DB, "Error in raop_metadata_send_thread: Could not init thread\n");
return NULL;
}
rmd = raop_metadata_prepare(ctx->id, ctx->rtptime);
if (!rmd)
return;
goto no_metadata;
for (rs = sessions; rs; rs = rs->next)
{
@ -2182,18 +2199,59 @@ raop_metadata_send(int id, uint64_t rtptime, uint64_t offset, int startup)
if (!rs->wants_metadata)
continue;
delay = (startup) ? RAOP_MD_DELAY_STARTUP : RAOP_MD_DELAY_SWITCH;
delay = (ctx->startup) ? RAOP_MD_DELAY_STARTUP : RAOP_MD_DELAY_SWITCH;
ret = raop_metadata_send_internal(rs, rmd, offset, delay);
ret = raop_metadata_send_internal(rs, rmd, ctx->offset, delay);
if (ret < 0)
{
raop_session_failure(rs);
continue;
}
}
no_metadata:
db_perthread_deinit();
free(ctx);
return NULL;
}
void
raop_metadata_send(int id, uint64_t rtptime, uint64_t offset, int startup)
{
struct raop_metadata_send_ctx *ctx;
pthread_t tid;
pthread_attr_t attr;
int ret;
ctx = malloc(sizeof(struct raop_metadata_send_ctx));
if (!ctx)
{
DPRINTF(E_LOG, L_RAOP, "Out of memory\n");
return;
}
ctx->id = id;
ctx->rtptime = rtptime;
ctx->offset = offset;
ctx->startup = startup;
ret = pthread_attr_init(&attr);
if (ret != 0)
{
DPRINTF(E_LOG, L_DB, "Error in raop_metadata_send: Could not init attributes\n");
return;
}
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
ret = pthread_create(&tid, &attr, raop_metadata_send_thread, ctx);
if (ret != 0)
{
DPRINTF(E_LOG, L_DB, "Error in raop_metadata_send: Could not create thread\n");
}
pthread_attr_destroy(&attr);
}
/* Volume handling */
static float

View File

@ -90,6 +90,7 @@ struct transcode_ctx {
uint32_t duration;
uint64_t samples;
uint32_t icy_hash;
/* WAV header */
int wavhdr;
@ -894,3 +895,41 @@ transcode_needed(const char *user_agent, const char *client_codecs, char *file_c
return 1;
}
void
transcode_metadata(struct transcode_ctx *ctx, struct icy_metadata **metadata, int *changed)
{
struct icy_metadata *m;
*metadata = NULL;
if (!ctx->fmtctx)
return;
m = icy_metadata_get(ctx->fmtctx, 1);
*changed = (m->hash != ctx->icy_hash);
ctx->icy_hash = m->hash;
*metadata = m;
}
void
transcode_metadata_artwork_url(struct transcode_ctx *ctx, char **artwork_url, char *stream_url)
{
struct icy_metadata *m;
*artwork_url = NULL;
if (!ctx->fmtctx || !ctx->fmtctx->filename)
return;
if (strcmp(ctx->fmtctx->filename, stream_url) != 0)
return;
m = icy_metadata_get(ctx->fmtctx, 1);
if (m->artwork_url)
*artwork_url = strdup(m->artwork_url);
}

View File

@ -7,6 +7,7 @@
#else
# include <event.h>
#endif
#include "icy.h"
struct transcode_ctx;
@ -25,4 +26,10 @@ transcode_cleanup(struct transcode_ctx *ctx);
int
transcode_needed(const char *user_agent, const char *client_codecs, char *file_codectype);
void
transcode_metadata(struct transcode_ctx *ctx, struct icy_metadata **metadata, int *changed);
void
transcode_metadata_artwork_url(struct transcode_ctx *ctx, char **artwork_url, char *stream_url);
#endif /* !__TRANSCODE_H__ */