2009-05-01 09:31:59 -04:00
|
|
|
/*
|
2010-01-05 13:34:00 -05:00
|
|
|
* Copyright (C) 2009-2010 Julien BLACHE <jb@jblache.org>
|
2009-05-01 09:31:59 -04:00
|
|
|
*
|
|
|
|
* Adapted from mt-daapd:
|
|
|
|
* Copyright (C) 2006-2007 Ron Pedde <ron@pedde.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 <string.h>
|
|
|
|
#include <unistd.h>
|
|
|
|
#include <sys/types.h>
|
|
|
|
#include <sys/stat.h>
|
|
|
|
#include <fcntl.h>
|
|
|
|
#include <stdint.h>
|
|
|
|
|
2010-01-17 04:47:46 -05:00
|
|
|
#if defined(__linux__) || defined(__GLIBC__)
|
2010-01-09 07:42:59 -05:00
|
|
|
# include <endian.h>
|
|
|
|
# include <byteswap.h>
|
2010-01-10 11:49:01 -05:00
|
|
|
#elif defined(__FreeBSD__) || defined(__FreeBSD_kernel__)
|
2010-01-09 07:42:59 -05:00
|
|
|
# include <sys/endian.h>
|
|
|
|
#endif
|
|
|
|
|
2009-05-01 09:31:59 -04:00
|
|
|
#include <event.h>
|
2009-05-02 11:58:38 -04:00
|
|
|
#include "evhttp/evhttp.h"
|
2009-05-01 09:31:59 -04:00
|
|
|
|
|
|
|
#include <libavcodec/avcodec.h>
|
|
|
|
#include <libavformat/avformat.h>
|
|
|
|
|
2009-05-08 11:46:32 -04:00
|
|
|
#include "logger.h"
|
2009-05-01 09:31:59 -04:00
|
|
|
#include "conffile.h"
|
2009-06-07 12:58:02 -04:00
|
|
|
#include "db.h"
|
2009-05-01 09:31:59 -04:00
|
|
|
#include "transcode.h"
|
|
|
|
|
|
|
|
|
|
|
|
#define XCODE_BUFFER_SIZE ((AVCODEC_MAX_AUDIO_FRAME_SIZE * 3) / 2)
|
|
|
|
#define RAW_BUFFER_SIZE 256
|
|
|
|
|
2009-11-18 14:14:03 -05:00
|
|
|
#define ID3V2_MIN_HEADER_SIZE 10
|
|
|
|
|
|
|
|
|
2009-05-01 09:31:59 -04:00
|
|
|
struct transcode_ctx {
|
|
|
|
AVFormatContext *fmtctx;
|
|
|
|
/* Audio stream */
|
|
|
|
int astream;
|
|
|
|
AVCodecContext *acodec; /* pCodecCtx */
|
|
|
|
AVCodec *adecoder; /* pCodec */
|
|
|
|
AVPacket apacket;
|
|
|
|
int apacket_size;
|
|
|
|
uint8_t *apacket_data;
|
2010-03-15 13:34:14 -04:00
|
|
|
int16_t *abuffer;
|
2009-05-01 09:31:59 -04:00
|
|
|
|
2010-03-15 13:38:33 -04:00
|
|
|
/* Resampling */
|
|
|
|
int need_resample;
|
|
|
|
int input_size;
|
|
|
|
ReSampleContext *resample_ctx;
|
|
|
|
int16_t *re_abuffer;
|
|
|
|
|
2009-05-01 09:31:59 -04:00
|
|
|
off_t offset;
|
|
|
|
|
|
|
|
uint32_t duration;
|
|
|
|
uint64_t samples;
|
|
|
|
|
|
|
|
/* WAV header */
|
|
|
|
uint8_t header[44];
|
|
|
|
|
|
|
|
/* Raw mode */
|
|
|
|
int fd;
|
|
|
|
uint8_t *rawbuffer;
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
static char *default_codecs = "mpeg,wav";
|
|
|
|
static char *roku_codecs = "mpeg,mp4a,wma,wav";
|
|
|
|
static char *itunes_codecs = "mpeg,mp4a,mp4v,alac,wav";
|
|
|
|
|
|
|
|
|
2009-11-18 14:14:03 -05:00
|
|
|
static int
|
|
|
|
id3v2_tag_len(const uint8_t *buf)
|
|
|
|
{
|
|
|
|
int len;
|
|
|
|
|
|
|
|
len = ID3V2_MIN_HEADER_SIZE
|
|
|
|
+ ((buf[6] & 0x7f) << 21)
|
|
|
|
+ ((buf[7] & 0x7f) << 14)
|
|
|
|
+ ((buf[8] & 0x7f) << 7)
|
|
|
|
+ (buf[9] & 0x7f);
|
|
|
|
|
|
|
|
if (buf[5] & 0x10)
|
|
|
|
len += ID3V2_MIN_HEADER_SIZE;
|
|
|
|
|
|
|
|
return len;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
has_id3v2_tag(const uint8_t *buf)
|
|
|
|
{
|
|
|
|
return ((buf[0] == 'I')
|
|
|
|
&& (buf[1] == 'D')
|
|
|
|
&& (buf[2] == '3')
|
|
|
|
&& (buf[3] != 0xff)
|
|
|
|
&& (buf[4] != 0xff)
|
|
|
|
&& ((buf[6] & 0x80) == 0)
|
|
|
|
&& ((buf[7] & 0x80) == 0)
|
|
|
|
&& ((buf[8] & 0x80) == 0)
|
|
|
|
&& ((buf[9] & 0x80) == 0));
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2009-05-01 09:31:59 -04:00
|
|
|
static inline void
|
|
|
|
add_le16(uint8_t *dst, uint16_t val)
|
|
|
|
{
|
|
|
|
dst[0] = val & 0xff;
|
|
|
|
dst[1] = (val >> 8) & 0xff;
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline void
|
|
|
|
add_le32(uint8_t *dst, uint32_t val)
|
|
|
|
{
|
|
|
|
dst[0] = val & 0xff;
|
|
|
|
dst[1] = (val >> 8) & 0xff;
|
|
|
|
dst[2] = (val >> 16) & 0xff;
|
|
|
|
dst[3] = (val >> 24) & 0xff;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
2010-02-02 15:02:24 -05:00
|
|
|
make_wav_header(struct transcode_ctx *ctx, off_t *est_size)
|
2009-05-01 09:31:59 -04:00
|
|
|
{
|
|
|
|
uint32_t wav_len;
|
|
|
|
int duration;
|
|
|
|
|
|
|
|
if (ctx->duration)
|
|
|
|
duration = ctx->duration;
|
|
|
|
else
|
|
|
|
duration = 3 * 60 * 1000; /* 3 minutes, in ms */
|
|
|
|
|
2010-03-15 13:38:33 -04:00
|
|
|
if (ctx->samples && !ctx->need_resample)
|
|
|
|
wav_len = 2 * 2 * ctx->samples;
|
2009-05-01 09:31:59 -04:00
|
|
|
else
|
2010-03-15 13:38:33 -04:00
|
|
|
wav_len = 2 * 2 * 44100 * (duration / 1000);
|
2009-05-01 09:31:59 -04:00
|
|
|
|
|
|
|
*est_size = wav_len + sizeof(ctx->header);
|
|
|
|
|
|
|
|
memcpy(ctx->header, "RIFF", 4);
|
|
|
|
add_le32(ctx->header + 4, 36 + wav_len);
|
|
|
|
memcpy(ctx->header + 8, "WAVEfmt ", 8);
|
|
|
|
add_le32(ctx->header + 16, 16);
|
|
|
|
add_le16(ctx->header + 20, 1);
|
2010-03-15 13:38:33 -04:00
|
|
|
add_le16(ctx->header + 22, 2); /* channels */
|
|
|
|
add_le32(ctx->header + 24, 44100); /* samplerate */
|
|
|
|
add_le32(ctx->header + 28, 44100 * 2 * 2); /* byte rate */
|
|
|
|
add_le16(ctx->header + 32, 2 * 2); /* block align */
|
|
|
|
add_le16(ctx->header + 34, 16); /* bits per sample */
|
2009-05-01 09:31:59 -04:00
|
|
|
memcpy(ctx->header + 36, "data", 4);
|
|
|
|
add_le32(ctx->header + 40, wav_len);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int
|
|
|
|
transcode(struct transcode_ctx *ctx, struct evbuffer *evbuf, int wanted)
|
|
|
|
{
|
2010-03-15 13:38:33 -04:00
|
|
|
int16_t *buf;
|
2009-05-01 09:31:59 -04:00
|
|
|
int buflen;
|
2010-03-15 13:38:33 -04:00
|
|
|
int processed;
|
2009-05-01 09:31:59 -04:00
|
|
|
int used;
|
|
|
|
int stop;
|
|
|
|
int ret;
|
|
|
|
#if BYTE_ORDER == BIG_ENDIAN
|
|
|
|
int i;
|
|
|
|
#endif
|
|
|
|
|
|
|
|
processed = 0;
|
|
|
|
|
|
|
|
if (ctx->offset == 0)
|
|
|
|
{
|
|
|
|
evbuffer_add(evbuf, ctx->header, sizeof(ctx->header));
|
|
|
|
processed += sizeof(ctx->header);
|
|
|
|
ctx->offset += sizeof(ctx->header);
|
|
|
|
}
|
|
|
|
|
|
|
|
stop = 0;
|
|
|
|
while ((processed < wanted) && !stop)
|
|
|
|
{
|
|
|
|
/* Decode data */
|
|
|
|
while (ctx->apacket_size > 0)
|
|
|
|
{
|
|
|
|
buflen = XCODE_BUFFER_SIZE;
|
|
|
|
|
|
|
|
used = avcodec_decode_audio2(ctx->acodec,
|
2010-03-15 13:34:14 -04:00
|
|
|
ctx->abuffer, &buflen,
|
2009-05-01 09:31:59 -04:00
|
|
|
ctx->apacket_data, ctx->apacket_size);
|
|
|
|
|
|
|
|
if (used < 0)
|
|
|
|
{
|
|
|
|
/* Something happened, skip this packet */
|
|
|
|
ctx->apacket_size = 0;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
ctx->apacket_data += used;
|
|
|
|
ctx->apacket_size -= used;
|
|
|
|
|
|
|
|
/* No frame decoded this time around */
|
|
|
|
if (buflen == 0)
|
|
|
|
continue;
|
|
|
|
|
2010-03-15 13:38:33 -04:00
|
|
|
if (ctx->need_resample)
|
|
|
|
{
|
|
|
|
buflen = audio_resample(ctx->resample_ctx, ctx->re_abuffer, ctx->abuffer, buflen / ctx->input_size);
|
|
|
|
|
|
|
|
if (buflen == 0)
|
|
|
|
{
|
|
|
|
DPRINTF(E_WARN, L_XCODE, "Resample returned no samples!\n");
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
buflen = buflen * 2 * 2; /* 16bit samples, 2 channels */
|
|
|
|
buf = ctx->re_abuffer;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
buf = ctx->abuffer;
|
|
|
|
|
2009-05-01 09:31:59 -04:00
|
|
|
#if BYTE_ORDER == BIG_ENDIAN
|
2010-03-15 13:38:33 -04:00
|
|
|
/* swap buffer, LE16 */
|
2009-05-01 09:31:59 -04:00
|
|
|
for (i = 0; i < (buflen / 2); i++)
|
|
|
|
{
|
|
|
|
buf[i] = htole16(buf[i]);
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2010-03-15 13:38:33 -04:00
|
|
|
ret = evbuffer_add(evbuf, buf, buflen);
|
2009-05-01 09:31:59 -04:00
|
|
|
if (ret != 0)
|
|
|
|
{
|
|
|
|
DPRINTF(E_WARN, L_XCODE, "Could not copy WAV data to buffer\n");
|
|
|
|
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
processed += buflen;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Read more data */
|
|
|
|
if (ctx->fd != -1)
|
|
|
|
{
|
|
|
|
/* Raw mode */
|
|
|
|
ret = read(ctx->fd, ctx->rawbuffer, RAW_BUFFER_SIZE);
|
|
|
|
if (ret <= 0)
|
|
|
|
{
|
|
|
|
DPRINTF(E_WARN, L_XCODE, "Could not read more raw data\n");
|
|
|
|
|
|
|
|
stop = 1;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
ctx->apacket_data = ctx->rawbuffer;
|
|
|
|
ctx->apacket_size = ret;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
/* ffmpeg mode */
|
|
|
|
do
|
|
|
|
{
|
|
|
|
if (ctx->apacket.data)
|
|
|
|
av_free_packet(&ctx->apacket);
|
|
|
|
|
|
|
|
ret = av_read_packet(ctx->fmtctx, &ctx->apacket);
|
|
|
|
if (ret < 0)
|
|
|
|
{
|
|
|
|
DPRINTF(E_WARN, L_XCODE, "Could not read more data\n");
|
|
|
|
|
|
|
|
stop = 1;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
while (ctx->apacket.stream_index != ctx->astream);
|
|
|
|
|
|
|
|
/* Copy apacket data & size and do not mess with them */
|
|
|
|
ctx->apacket_data = ctx->apacket.data;
|
|
|
|
ctx->apacket_size = ctx->apacket.size;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
ctx->offset += processed;
|
|
|
|
|
|
|
|
return processed;
|
|
|
|
}
|
|
|
|
|
|
|
|
struct transcode_ctx *
|
2010-02-02 15:02:24 -05:00
|
|
|
transcode_setup(struct media_file_info *mfi, off_t *est_size)
|
2009-05-01 09:31:59 -04:00
|
|
|
{
|
|
|
|
struct transcode_ctx *ctx;
|
2010-02-10 12:19:32 -05:00
|
|
|
off_t pos;
|
2009-11-18 14:14:03 -05:00
|
|
|
int hdr_len;
|
2009-05-01 09:31:59 -04:00
|
|
|
int i;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
ctx = (struct transcode_ctx *)malloc(sizeof(struct transcode_ctx));
|
|
|
|
if (!ctx)
|
|
|
|
{
|
|
|
|
DPRINTF(E_WARN, L_XCODE, "Could not allocate transcode context\n");
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
memset(ctx, 0, sizeof(struct transcode_ctx));
|
|
|
|
ctx->fd = -1;
|
|
|
|
|
|
|
|
ret = av_open_input_file(&ctx->fmtctx, mfi->path, NULL, 0, NULL);
|
|
|
|
if (ret != 0)
|
|
|
|
{
|
|
|
|
DPRINTF(E_WARN, L_XCODE, "Could not open file %s: %s\n", mfi->fname, strerror(AVUNERROR(ret)));
|
|
|
|
|
|
|
|
free(ctx);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = av_find_stream_info(ctx->fmtctx);
|
|
|
|
if (ret < 0)
|
|
|
|
{
|
|
|
|
DPRINTF(E_WARN, L_XCODE, "Could not find stream info: %s\n", strerror(AVUNERROR(ret)));
|
|
|
|
|
|
|
|
goto setup_fail;
|
|
|
|
}
|
|
|
|
|
|
|
|
ctx->astream = -1;
|
|
|
|
for (i = 0; i < ctx->fmtctx->nb_streams; i++)
|
|
|
|
{
|
|
|
|
if (ctx->fmtctx->streams[i]->codec->codec_type == CODEC_TYPE_AUDIO)
|
|
|
|
{
|
|
|
|
ctx->astream = i;
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (ctx->astream < 0)
|
|
|
|
{
|
|
|
|
DPRINTF(E_WARN, L_XCODE, "No audio stream found in file %s\n", mfi->fname);
|
|
|
|
|
|
|
|
goto setup_fail;
|
|
|
|
}
|
|
|
|
|
|
|
|
ctx->acodec = ctx->fmtctx->streams[ctx->astream]->codec;
|
|
|
|
|
|
|
|
ctx->adecoder = avcodec_find_decoder(ctx->acodec->codec_id);
|
|
|
|
if (!ctx->adecoder)
|
|
|
|
{
|
|
|
|
DPRINTF(E_WARN, L_XCODE, "No suitable decoder found for codec\n");
|
|
|
|
|
|
|
|
goto setup_fail;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (ctx->adecoder->capabilities & CODEC_CAP_TRUNCATED)
|
|
|
|
ctx->acodec->flags |= CODEC_FLAG_TRUNCATED;
|
|
|
|
|
|
|
|
ret = avcodec_open(ctx->acodec, ctx->adecoder);
|
|
|
|
if (ret != 0)
|
|
|
|
{
|
|
|
|
DPRINTF(E_WARN, L_XCODE, "Could not open codec: %s\n", strerror(AVUNERROR(ret)));
|
|
|
|
|
|
|
|
goto setup_fail;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* FLAC needs raw mode, ffmpeg sucks */
|
|
|
|
if (ctx->acodec->codec_id == CODEC_ID_FLAC)
|
|
|
|
{
|
|
|
|
ctx->rawbuffer = (uint8_t *)malloc(RAW_BUFFER_SIZE);
|
|
|
|
if (!ctx->rawbuffer)
|
|
|
|
{
|
|
|
|
DPRINTF(E_WARN, L_XCODE, "Could not allocate raw buffer\n");
|
|
|
|
|
|
|
|
avcodec_close(ctx->acodec);
|
|
|
|
goto setup_fail;
|
|
|
|
}
|
|
|
|
|
|
|
|
ctx->fd = open(mfi->path, O_RDONLY);
|
|
|
|
if (ctx->fd < 0)
|
|
|
|
{
|
|
|
|
DPRINTF(E_WARN, L_XCODE, "Could not open %s: %s\n", mfi->fname, strerror(errno));
|
|
|
|
|
2009-11-18 14:14:03 -05:00
|
|
|
free(ctx->rawbuffer);
|
|
|
|
avcodec_close(ctx->acodec);
|
|
|
|
goto setup_fail;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Check for ID3v2 header */
|
|
|
|
ret = read(ctx->fd, ctx->rawbuffer, ID3V2_MIN_HEADER_SIZE);
|
|
|
|
if ((ret < 0) || (ret != ID3V2_MIN_HEADER_SIZE))
|
|
|
|
{
|
|
|
|
if (ret < 0)
|
|
|
|
DPRINTF(E_WARN, L_XCODE, "Could not read raw data: %s\n", strerror(errno));
|
|
|
|
else
|
|
|
|
DPRINTF(E_WARN, L_XCODE, "Could not read enough raw data\n");
|
|
|
|
|
2010-03-15 13:35:29 -04:00
|
|
|
goto setup_fail_codec;
|
2009-11-18 14:14:03 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
ret = has_id3v2_tag(ctx->rawbuffer);
|
|
|
|
if (ret)
|
|
|
|
{
|
|
|
|
hdr_len = id3v2_tag_len(ctx->rawbuffer);
|
|
|
|
DPRINTF(E_DBG, L_XCODE, "Skipping ID3V2 header of %d bytes\n", hdr_len);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
hdr_len = 0;
|
|
|
|
|
2010-02-10 12:19:32 -05:00
|
|
|
pos = lseek(ctx->fd, hdr_len, SEEK_SET);
|
|
|
|
if (pos == (off_t) -1)
|
2009-11-18 14:14:03 -05:00
|
|
|
{
|
|
|
|
DPRINTF(E_WARN, L_XCODE, "Could not seek: %s\n", strerror(errno));
|
|
|
|
|
2010-03-15 13:35:29 -04:00
|
|
|
goto setup_fail_codec;
|
2009-05-01 09:31:59 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (ctx->fd != -1)
|
|
|
|
DPRINTF(E_DBG, L_XCODE, "Set up raw mode for transcoding input\n");
|
|
|
|
|
2010-03-15 13:34:14 -04:00
|
|
|
ctx->abuffer = (int16_t *)av_malloc(XCODE_BUFFER_SIZE);
|
2009-05-01 09:31:59 -04:00
|
|
|
if (!ctx->abuffer)
|
|
|
|
{
|
|
|
|
DPRINTF(E_WARN, L_XCODE, "Could not allocate transcode buffer\n");
|
|
|
|
|
2010-03-15 13:35:29 -04:00
|
|
|
goto setup_fail_codec;
|
2009-05-01 09:31:59 -04:00
|
|
|
}
|
|
|
|
|
2010-03-15 13:38:33 -04:00
|
|
|
if ((ctx->acodec->sample_fmt != SAMPLE_FMT_S16)
|
|
|
|
|| (ctx->acodec->channels != 2)
|
|
|
|
|| (ctx->acodec->sample_rate != 44100))
|
|
|
|
{
|
|
|
|
DPRINTF(E_DBG, L_XCODE, "Setting up resampling (%d@%d)\n", ctx->acodec->channels, ctx->acodec->sample_rate);
|
|
|
|
|
|
|
|
ctx->resample_ctx = av_audio_resample_init(2, ctx->acodec->channels,
|
|
|
|
44100, ctx->acodec->sample_rate,
|
|
|
|
SAMPLE_FMT_S16, ctx->acodec->sample_fmt,
|
|
|
|
16, 10, 0, 0.8);
|
|
|
|
|
|
|
|
if (!ctx->resample_ctx)
|
|
|
|
{
|
|
|
|
DPRINTF(E_WARN, L_XCODE, "Could not init resample from %d@%d to 2@44100\n", ctx->acodec->channels, ctx->acodec->sample_rate);
|
|
|
|
|
|
|
|
goto setup_fail_codec;
|
|
|
|
}
|
|
|
|
|
|
|
|
ctx->re_abuffer = (int16_t *)av_malloc(XCODE_BUFFER_SIZE * 2);
|
|
|
|
if (!ctx->re_abuffer)
|
|
|
|
{
|
|
|
|
DPRINTF(E_WARN, L_XCODE, "Could not allocate resample buffer\n");
|
|
|
|
|
|
|
|
audio_resample_close(ctx->resample_ctx);
|
|
|
|
goto setup_fail_codec;
|
|
|
|
}
|
|
|
|
|
|
|
|
ctx->need_resample = 1;
|
|
|
|
ctx->input_size = ctx->acodec->channels * av_get_bits_per_sample_format(ctx->acodec->sample_fmt) / 8;
|
|
|
|
}
|
|
|
|
|
2009-05-01 09:31:59 -04:00
|
|
|
ctx->duration = mfi->song_length;
|
|
|
|
ctx->samples = mfi->sample_count;
|
|
|
|
|
|
|
|
make_wav_header(ctx, est_size);
|
|
|
|
|
|
|
|
return ctx;
|
|
|
|
|
2010-03-15 13:35:29 -04:00
|
|
|
setup_fail_codec:
|
|
|
|
if (ctx->fd != -1)
|
|
|
|
{
|
|
|
|
close(ctx->fd);
|
|
|
|
free(ctx->rawbuffer);
|
|
|
|
}
|
|
|
|
avcodec_close(ctx->acodec);
|
|
|
|
|
2009-05-01 09:31:59 -04:00
|
|
|
setup_fail:
|
|
|
|
av_close_input_file(ctx->fmtctx);
|
|
|
|
free(ctx);
|
2010-03-15 13:35:29 -04:00
|
|
|
|
2009-05-01 09:31:59 -04:00
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
transcode_cleanup(struct transcode_ctx *ctx)
|
|
|
|
{
|
|
|
|
if (ctx->fd != -1)
|
|
|
|
{
|
|
|
|
close(ctx->fd);
|
|
|
|
free(ctx->rawbuffer);
|
|
|
|
}
|
|
|
|
if (ctx->apacket.data)
|
|
|
|
av_free_packet(&ctx->apacket);
|
2010-03-15 13:38:33 -04:00
|
|
|
|
2009-05-01 09:31:59 -04:00
|
|
|
avcodec_close(ctx->acodec);
|
|
|
|
av_close_input_file(ctx->fmtctx);
|
|
|
|
|
2009-07-24 02:19:31 -04:00
|
|
|
av_free(ctx->abuffer);
|
2010-03-15 13:38:33 -04:00
|
|
|
|
|
|
|
if (ctx->need_resample)
|
|
|
|
{
|
|
|
|
audio_resample_close(ctx->resample_ctx);
|
|
|
|
av_free(ctx->re_abuffer);
|
|
|
|
}
|
|
|
|
|
2009-05-01 09:31:59 -04:00
|
|
|
free(ctx);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int
|
|
|
|
transcode_needed(struct evkeyvalq *headers, char *file_codectype)
|
|
|
|
{
|
|
|
|
const char *client_codecs;
|
|
|
|
const char *user_agent;
|
|
|
|
char *codectype;
|
|
|
|
cfg_t *lib;
|
|
|
|
int size;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
DPRINTF(E_DBG, L_XCODE, "Determining transcoding status for codectype %s\n", file_codectype);
|
|
|
|
|
2010-03-19 14:06:47 -04:00
|
|
|
lib = cfg_getsec(cfg, "library");
|
2009-05-01 09:31:59 -04:00
|
|
|
|
|
|
|
size = cfg_size(lib, "no_transcode");
|
|
|
|
if (size > 0)
|
|
|
|
{
|
|
|
|
for (i = 0; i < size; i++)
|
|
|
|
{
|
|
|
|
codectype = cfg_getnstr(lib, "no_transcode", i);
|
|
|
|
|
|
|
|
if (strcmp(file_codectype, codectype) == 0)
|
|
|
|
{
|
|
|
|
DPRINTF(E_DBG, L_XCODE, "Codectype is in no_transcode\n");
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
size = cfg_size(lib, "force_transcode");
|
|
|
|
if (size > 0)
|
|
|
|
{
|
|
|
|
for (i = 0; i < size; i++)
|
|
|
|
{
|
|
|
|
codectype = cfg_getnstr(lib, "force_transcode", i);
|
|
|
|
|
|
|
|
if (strcmp(file_codectype, codectype) == 0)
|
|
|
|
{
|
|
|
|
DPRINTF(E_DBG, L_XCODE, "Codectype is in force_transcode\n");
|
|
|
|
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
client_codecs = evhttp_find_header(headers, "Accept-Codecs");
|
|
|
|
if (!client_codecs)
|
|
|
|
{
|
|
|
|
user_agent = evhttp_find_header(headers, "User-Agent");
|
|
|
|
if (user_agent)
|
|
|
|
{
|
|
|
|
DPRINTF(E_DBG, L_XCODE, "User-Agent: %s\n", user_agent);
|
|
|
|
|
|
|
|
if (strncmp(user_agent, "iTunes", strlen("iTunes")) == 0)
|
|
|
|
{
|
|
|
|
DPRINTF(E_DBG, L_XCODE, "Client is iTunes\n");
|
|
|
|
|
2009-12-08 14:45:57 -05:00
|
|
|
client_codecs = itunes_codecs;
|
|
|
|
}
|
|
|
|
else if (strncmp(user_agent, "QuickTime", strlen("QuickTime")) == 0)
|
|
|
|
{
|
|
|
|
DPRINTF(E_DBG, L_XCODE, "Client is QuickTime, using iTunes codecs\n");
|
|
|
|
|
|
|
|
client_codecs = itunes_codecs;
|
|
|
|
}
|
|
|
|
else if (strncmp(user_agent, "Front%20Row", strlen("Front%20Row")) == 0)
|
|
|
|
{
|
|
|
|
DPRINTF(E_DBG, L_XCODE, "Client is Front Row, using iTunes codecs\n");
|
|
|
|
|
2009-12-30 12:49:52 -05:00
|
|
|
client_codecs = itunes_codecs;
|
|
|
|
}
|
|
|
|
else if (strncmp(user_agent, "Remote", strlen("Remote")) == 0)
|
|
|
|
{
|
|
|
|
DPRINTF(E_DBG, L_XCODE, "Client is Remote, using iTunes codecs\n");
|
|
|
|
|
2009-05-01 09:31:59 -04:00
|
|
|
client_codecs = itunes_codecs;
|
|
|
|
}
|
|
|
|
else if (strncmp(user_agent, "Roku", strlen("Roku")) == 0)
|
|
|
|
{
|
|
|
|
DPRINTF(E_DBG, L_XCODE, "Client is a Roku device\n");
|
|
|
|
|
|
|
|
client_codecs = roku_codecs;
|
|
|
|
}
|
|
|
|
else if (strncmp(user_agent, "Hifidelio", strlen("Hifidelio")) == 0)
|
|
|
|
{
|
|
|
|
DPRINTF(E_DBG, L_XCODE, "Client is a Hifidelio device, allegedly cannot transcode\n");
|
|
|
|
|
|
|
|
/* Allegedly can't transcode for Hifidelio because their
|
|
|
|
* HTTP implementation doesn't honour Connection: close.
|
|
|
|
* At least, that's why mt-daapd didn't do it.
|
|
|
|
*/
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
DPRINTF(E_DBG, L_XCODE, "Client advertises codecs: %s\n", client_codecs);
|
|
|
|
|
|
|
|
if (!client_codecs)
|
|
|
|
{
|
|
|
|
DPRINTF(E_DBG, L_XCODE, "Could not identify client, using default codectype set\n");
|
|
|
|
|
|
|
|
client_codecs = default_codecs;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (strstr(client_codecs, file_codectype))
|
|
|
|
{
|
|
|
|
DPRINTF(E_DBG, L_XCODE, "Codectype supported by client, no transcoding needed\n");
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
DPRINTF(E_DBG, L_XCODE, "Will transcode\n");
|
|
|
|
|
|
|
|
return 1;
|
|
|
|
}
|