Commit 5bf01e70 authored by Frédéric Yhuel's avatar Frédéric Yhuel Committed by Jean-Baptiste Kempf
parent 5a72f814
......@@ -92,6 +92,15 @@ libdash_plugin_la_CXXFLAGS = $(AM_CFLAGS) -I$(srcdir)/dash
libdash_plugin_la_LIBADD = $(AM_LIBADD) $(SOCKET_LIBS)
libvlc_LTLIBRARIES += libdash_plugin.la
libsmooth_plugin_la_SOURCES = \
smooth/smooth.c \
smooth/utils.c \
smooth/downloader.c \
smooth/smooth.h
libsmooth_plugin_la_CFLAGS = $(AM_CFLAGS)
libvlc_LTLIBRARIES += libsmooth_plugin.la
libhttplive_plugin_la_SOURCES = httplive.c
libhttplive_plugin_la_CFLAGS = $(AM_CFLAGS) $(GCRYPT_CFLAGS)
libhttplive_plugin_la_LIBADD = $(AM_LIBADD) $(GCRYPT_LIBS) -lgpg-error
......
/*****************************************************************************
* downloader.c: download thread
*****************************************************************************
* Copyright (C) 1996-2012 VLC authors and VideoLAN
* $Id$
*
* Author: Frédéric Yhuel <fyhuel _AT_ viotech _DOT_ net>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*****************************************************************************/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include <vlc_common.h>
#include <assert.h>
#include <vlc_stream.h>
#include <vlc_es.h>
#include "smooth.h"
#include "../../demux/mp4/libmp4.h"
static char *ConstructUrl( const char *template, const char *base_url,
const uint64_t bandwidth, const uint64_t start_time )
{
char *frag, *end, *qual;
char *url_template = strdup( template );
char *saveptr = NULL;
qual = strtok_r( url_template, "{", &saveptr );
strtok_r( NULL, "}", &saveptr );
frag = strtok_r( NULL, "{", &saveptr );
strtok_r( NULL, "}", &saveptr );
end = strtok_r( NULL, "", &saveptr );
char *url = NULL;
if( asprintf( &url, "%s/%s%"PRIu64"%s%"PRIu64"%s", base_url, qual,
bandwidth, frag, start_time, end) < 0 )
{
return NULL;
}
free( url_template );
return url;
}
static chunk_t * chunk_Get( sms_stream_t *sms, const int64_t start_time )
{
int len = vlc_array_count( sms->chunks );
for( int i = 0; i < len; i++ )
{
chunk_t * chunk = vlc_array_item_at_index( sms->chunks, i );
if( !chunk ) return NULL;
if( chunk->start_time <= start_time &&
chunk->start_time + chunk->duration > start_time )
{
return chunk;
}
}
return NULL;
}
static unsigned set_track_id( chunk_t *chunk, const unsigned tid )
{
uint32_t size, type;
if( !chunk->data )
return 0;
uint8_t *slice = chunk->data->p_buffer;
if( !slice )
return 0;
SMS_GET4BYTES( size );
SMS_GETFOURCC( type );
assert( type == ATOM_moof );
SMS_GET4BYTES( size );
SMS_GETFOURCC( type );
assert( type == ATOM_mfhd );
slice += size - 8;
SMS_GET4BYTES( size );
SMS_GETFOURCC( type );
assert( type == ATOM_traf );
SMS_GET4BYTES( size );
SMS_GETFOURCC( type );
if( type != ATOM_tfhd )
return 0;
unsigned ret = bswap32( ((uint32_t *)slice)[1] );
((uint32_t *)slice)[1] = bswap32( tid );
return ret;
}
static int sms_Download( stream_t *s, chunk_t *chunk, char *url )
{
stream_sys_t *p_sys = s->p_sys;
stream_t *p_ts = stream_UrlNew( s, url );
free( url );
if( p_ts == NULL )
return VLC_EGENERIC;
int64_t size = stream_Size( p_ts );
chunk->size = size;
chunk->offset = p_sys->download.next_chunk_offset;
p_sys->download.next_chunk_offset += chunk->size;
chunk->data = block_Alloc( size );
if( chunk->data == NULL )
{
stream_Delete( p_ts );
return VLC_ENOMEM;
}
int read = stream_Read( p_ts, chunk->data->p_buffer, size );
if( read < size )
{
msg_Warn( s, "sms_Download: I requested %"PRIi64" bytes, "\
"but I got only %i", size, read );
chunk->data = block_Realloc( chunk->data, 0, read );
}
stream_Delete( p_ts );
vlc_mutex_lock( &p_sys->download.lock_wait );
if( chunk->type == AUDIO_ES )
p_sys->download.alead += chunk->duration;
else if( chunk->type == VIDEO_ES )
p_sys->download.vlead += chunk->duration;
else if( chunk->type == SPU_ES )
p_sys->download.tlead += chunk->duration;
vlc_mutex_unlock( &p_sys->download.lock_wait );
return VLC_SUCCESS;
}
#ifdef DISABLE_BANDWIDTH_ADAPTATION
static unsigned BandwidthAdaptation( stream_t *s,
sms_stream_t *sms, uint64_t *bandwidth )
{
return sms->download_qlvl;
}
#else
static unsigned BandwidthAdaptation( stream_t *s,
sms_stream_t *sms, uint64_t bandwidth )
{
if( sms->type != VIDEO_ES )
return sms->download_qlvl;
uint64_t bw_candidate = 0;
quality_level_t *qlevel;
unsigned ret = sms->download_qlvl;
for( unsigned i = 0; i < sms->qlevel_nb; i++ )
{
qlevel = vlc_array_item_at_index( sms->qlevels, i );
if( unlikely( !qlevel ) )
{
msg_Err( s, "Could no get %uth quality level", i );
return 0;
}
if( qlevel->Bitrate < (bandwidth - bandwidth / 3) &&
qlevel->Bitrate > bw_candidate )
{
bw_candidate = qlevel->Bitrate;
ret = qlevel->id;
}
}
return ret;
}
#endif
static int get_new_chunks( stream_t *s, chunk_t *ck )
{
stream_sys_t *p_sys = s->p_sys;
uint8_t *slice = ck->data->p_buffer;
if( !slice )
return VLC_EGENERIC;
uint8_t version, fragment_count;
uint32_t size, type, flags;
sms_stream_t *sms;
UUID_t uuid;
TfrfBoxDataFields_t *tfrf_df;
if( ck->type == AUDIO_ES )
sms = p_sys->astream;
else if ( ck->type == VIDEO_ES )
sms = p_sys->vstream;
else
return 0;
SMS_GET4BYTES( size );
SMS_GETFOURCC( type );
assert( type == ATOM_moof );
SMS_GET4BYTES( size );
SMS_GETFOURCC( type );
assert( type == ATOM_mfhd );
slice += size - 8;
SMS_GET4BYTES( size );
SMS_GETFOURCC( type );
assert( type == ATOM_traf );
for(;;)
{
SMS_GET4BYTES( size );
assert( size > 1 );
SMS_GETFOURCC( type );
if( type == ATOM_mdat )
{
msg_Err( s, "No uuid box found :-(" );
return VLC_EGENERIC;
}
else if( type == ATOM_uuid )
{
GetUUID( &uuid, slice);
if( !CmpUUID( &uuid, &TfrfBoxUUID ) )
break;
}
slice += size - 8;
}
slice += 16;
SMS_GET1BYTE( version );
SMS_GET3BYTES( flags );
SMS_GET1BYTE( fragment_count );
tfrf_df = calloc( fragment_count, sizeof( TfrfBoxDataFields_t ) );
if( unlikely( tfrf_df == NULL ) )
return VLC_EGENERIC;
for( uint8_t i = 0; i < fragment_count; i++ )
{
SMS_GET4or8BYTES( tfrf_df[i].i_fragment_abs_time );
SMS_GET4or8BYTES( tfrf_df[i].i_fragment_duration );
}
msg_Dbg( s, "read box: \"tfrf\" version %d, flags 0x%x, "\
"fragment count %"PRIu8, version, flags, fragment_count );
for( uint8_t i = 0; i < fragment_count; i++ )
{
int64_t dur = tfrf_df[i].i_fragment_duration;
int64_t stime = tfrf_df[i].i_fragment_abs_time;
msg_Dbg( s, "\"tfrf\" fragment duration %"PRIu64", "\
"fragment abs time %"PRIu64, dur, stime);
if( !chunk_Get( sms, stime + dur ) )
chunk_New( sms, dur, stime );
}
free( tfrf_df );
return VLC_SUCCESS;
}
#define STRA_SIZE 342
#define SMOO_SIZE (STRA_SIZE * 3 + 24) /* 1050 */
/* SmooBox is a very simple MP4 box, used only to pass information
* to the demux layer. As this box is not aimed to travel accross networks,
* simplicity of the design is better than compactness */
static int build_smoo_box( stream_t *s, uint8_t *smoo_box )
{
stream_sys_t *p_sys = s->p_sys;
sms_stream_t *sms = NULL;
/* smoo */
memset( smoo_box, 0, SMOO_SIZE );
smoo_box[2] = (SMOO_SIZE & 0xff00)>>8;
smoo_box[3] = SMOO_SIZE & 0xff;
smoo_box[4] = 'u';
smoo_box[5] = 'u';
smoo_box[6] = 'i';
smoo_box[7] = 'd';
/* UUID is e1da72ba-24d7-43c3-a6a5-1b5759a1a92c */
((uint32_t *)smoo_box)[2] = bswap32( 0xe1da72ba );
((uint32_t *)smoo_box)[3] = bswap32( 0x24d743c3 );
((uint32_t *)smoo_box)[4] = bswap32( 0xa6a51b57 );
((uint32_t *)smoo_box)[5] = bswap32( 0x59a1a92c );
uint8_t *stra_box;
for( int i = 0; i < 3; i++ )
{
sms = NULL;
stra_box = smoo_box + i * STRA_SIZE;
stra_box[26] = (STRA_SIZE & 0xff00)>>8;
stra_box[27] = STRA_SIZE & 0xff;
stra_box[28] = 'u';
stra_box[29] = 'u';
stra_box[30] = 'i';
stra_box[31] = 'd';
/* UUID is b03ef770-33bd-4bac-96c7-bf25f97e2447 */
((uint32_t *)stra_box)[8] = bswap32( 0xb03ef770 );
((uint32_t *)stra_box)[9] = bswap32( 0x33bd4bac );
((uint32_t *)stra_box)[10] = bswap32( 0x96c7bf25 );
((uint32_t *)stra_box)[11] = bswap32( 0xf97e2447 );
if( i == 0)
{
stra_box[48] = VIDEO_ES;
sms = p_sys->vstream;
}
else if( i == 1 )
{
stra_box[48] = AUDIO_ES;
sms = p_sys->astream;
}
else if( i == 2 )
{
stra_box[48] = SPU_ES;
sms = p_sys->tstream;
}
stra_box[49] = 0; /* reserved */
if( sms == NULL )
continue;
stra_box[50] = (sms->id & 0xff00)>>8;
stra_box[51] = sms->id & 0xff;
((uint32_t *)stra_box)[13] = bswap32( sms->timescale );
((uint64_t *)stra_box)[7] = bswap64( p_sys->vod_duration );
quality_level_t * qlvl = get_qlevel( sms, sms->download_qlvl );
((uint32_t *)stra_box)[16] = bswap32( qlvl->FourCC );
((uint32_t *)stra_box)[17] = bswap32( qlvl->Bitrate );
((uint32_t *)stra_box)[18] = bswap32( qlvl->MaxWidth );
((uint32_t *)stra_box)[19] = bswap32( qlvl->MaxHeight );
((uint32_t *)stra_box)[20] = bswap32( qlvl->SamplingRate );
((uint32_t *)stra_box)[21] = bswap32( qlvl->Channels );
((uint32_t *)stra_box)[22] = bswap32( qlvl->BitsPerSample );
((uint32_t *)stra_box)[23] = bswap32( qlvl->PacketSize );
((uint32_t *)stra_box)[24] = bswap32( qlvl->AudioTag );
((uint32_t *)stra_box)[25] = bswap32( qlvl->AvgBytesPerSec );
((uint16_t *)stra_box)[52] = bswap16( qlvl->nBlockAlign );
stra_box[106] = stra_box[107] = stra_box[108] = 0; /* reserved */
assert( strlen( qlvl->CodecPrivateData ) < 512 );
stra_box[109] = strlen( qlvl->CodecPrivateData ) / 2;
uint8_t *binary_cpd = decode_string_hex_to_binary( qlvl->CodecPrivateData );
memcpy( stra_box + 110, binary_cpd, stra_box[109] );
free( binary_cpd );
}
return VLC_SUCCESS;
}
static chunk_t *build_init_chunk( stream_t *s )
{
chunk_t *ret = calloc( 1, sizeof( chunk_t ) );
if( unlikely( ret == NULL ) )
goto build_init_chunk_error;
ret->size = SMOO_SIZE;
ret->data = block_Alloc( SMOO_SIZE );
if( !ret->data )
goto build_init_chunk_error;
int res = build_smoo_box( s, ret->data->p_buffer );
if( res != VLC_SUCCESS )
goto build_init_chunk_error;
return ret;
build_init_chunk_error:
msg_Err( s, "build_init_chunk failed" );
return NULL;
}
static int Download( stream_t *s, sms_stream_t *sms )
{
stream_sys_t *p_sys = s->p_sys;
int64_t start_time;
if( sms->type == AUDIO_ES )
start_time = p_sys->download.alead;
else if ( sms->type == VIDEO_ES )
start_time = p_sys->download.vlead;
else
{
return VLC_EGENERIC;
}
quality_level_t *qlevel = get_qlevel( sms, sms->download_qlvl );
if( unlikely( !qlevel ) )
{
msg_Err( s, "Could not get quality level id %u", sms->download_qlvl );
return VLC_EGENERIC;
}
chunk_t *chunk = chunk_Get( sms, start_time );
if( !chunk )
{
msg_Warn( s, "Could not find a chunk for stream %s, "\
"start time = %"PRIu64"", sms->name, start_time );
return VLC_EGENERIC;
}
if( chunk->data != NULL )
{
/* Segment already downloaded */
msg_Warn( s, "Segment already downloaded" );
return VLC_SUCCESS;
}
chunk->type = sms->type;
char *url = ConstructUrl( sms->url_template, p_sys->base_url,
qlevel->Bitrate, chunk->start_time );
if( !url )
{
msg_Err( s, "ConstructUrl returned NULL" );
return VLC_EGENERIC;
}
/* sanity check - can we download this chunk on time? */
uint64_t avg_bw = sms_queue_avg( p_sys->bws );
if( (avg_bw > 0) && (qlevel->Bitrate > 0) )
{
/* duration in ms */
unsigned chunk_duration = chunk->duration * 1000 / sms->timescale;
uint64_t size = chunk_duration * qlevel->Bitrate / 1000; /* bits */
unsigned estimated = size * 1000 / avg_bw;
if( estimated > chunk_duration )
{
msg_Warn( s,"downloading of chunk %d would take %d ms, "\
"which is longer than its playback (%d ms)",
chunk->sequence, estimated, chunk_duration );
}
}
mtime_t start = mdate();
if( sms_Download( s, chunk, url ) != VLC_SUCCESS )
{
msg_Err( s, "downloaded chunk %u from stream %s at quality\
%u failed", chunk->sequence, sms->name, qlevel->Bitrate );
return VLC_EGENERIC;
}
mtime_t duration = mdate() - start;
unsigned real_id = set_track_id( chunk, sms->id );
if( real_id == 0)
{
msg_Err( s, "tfhd box not found or invalid chunk" );
return VLC_EGENERIC;
}
//msg_Dbg( s, "chunk ID was %i and is now %i", real_id, sms->id );
if( p_sys->b_live )
get_new_chunks( s, chunk );
vlc_mutex_lock( &p_sys->download.lock_wait );
vlc_array_append( p_sys->download.chunks, chunk );
vlc_cond_signal( &p_sys->download.wait );
vlc_mutex_unlock( &p_sys->download.lock_wait );
msg_Info( s, "downloaded chunk %d from stream %s at quality %u",
chunk->sequence, sms->name, qlevel->Bitrate );
uint64_t actual_lead = chunk->start_time + chunk->duration;
if( sms->type == AUDIO_ES )
{
p_sys->download.aindex = chunk->sequence;
p_sys->download.alead = __MIN( p_sys->download.alead, actual_lead );
}
else if( sms->type == VIDEO_ES )
{
p_sys->download.vindex = chunk->sequence;
p_sys->download.vlead = __MIN( p_sys->download.vlead, actual_lead );
p_sys->playback.toffset = __MIN( p_sys->playback.toffset, (uint64_t)chunk->start_time );
}
else if( sms->type == SPU_ES )
{
p_sys->download.sindex = chunk->sequence;
p_sys->download.tlead = __MIN( p_sys->download.tlead, actual_lead );
}
unsigned dur_ms = __MAX( 1, duration / 1000 );
uint64_t bw = chunk->size * 8 * 1000 / dur_ms; /* bits / s */
if( sms_queue_put( p_sys->bws, bw ) != VLC_SUCCESS )
return VLC_EGENERIC;
avg_bw = sms_queue_avg( p_sys->bws );
if( sms->type != VIDEO_ES )
return VLC_SUCCESS;
/* Track could get disabled in mp4 demux if we trigger adaption too soon. */
if( chunk->sequence <= 1 )
return VLC_SUCCESS;
unsigned new_qlevel_id = BandwidthAdaptation( s, sms, avg_bw );
quality_level_t *new_qlevel = get_qlevel( sms, new_qlevel_id );
if( unlikely( !new_qlevel ) )
{
msg_Err( s, "Could not get quality level id %u", new_qlevel_id );
return VLC_EGENERIC;
}
if( new_qlevel->Bitrate != qlevel->Bitrate )
{
msg_Warn( s, "detected %s bandwidth (%u) stream",
(new_qlevel->Bitrate >= qlevel->Bitrate) ? "faster" : "lower",
new_qlevel->Bitrate );
sms->download_qlvl = new_qlevel_id;
}
if( new_qlevel->MaxWidth != qlevel->MaxWidth ||
new_qlevel->MaxHeight != qlevel->MaxHeight )
{
chunk_t *new_init_ck = build_init_chunk( s );
if( !new_init_ck )
{
return VLC_EGENERIC;
}
new_init_ck->offset = p_sys->download.next_chunk_offset;
p_sys->download.next_chunk_offset += new_init_ck->size;
vlc_mutex_lock( &p_sys->download.lock_wait );
vlc_array_append( p_sys->download.chunks, new_init_ck );
vlc_mutex_unlock( &p_sys->download.lock_wait );
}
return VLC_SUCCESS;
}
void* sms_Thread( void *p_this )
{
stream_t *s = (stream_t *)p_this;
stream_sys_t *p_sys = s->p_sys;
int canc = vlc_savecancel();
sms_stream_t *vsms = p_sys->vstream;
sms_stream_t *asms = p_sys->astream;
if( !asms || !vsms )
goto cancel;
/* We compute the average bandwidth of the 4 last downloaded
* chunks, but feel free to replace '4' by whatever you wish */
p_sys->bws = sms_queue_init( 4 );
if( !p_sys->bws )
goto cancel;
chunk_t *init_ck = build_init_chunk( s );
if( !init_ck )
goto cancel;
vlc_mutex_lock( &p_sys->download.lock_wait );
vlc_array_append( p_sys->download.chunks, init_ck );
vlc_mutex_unlock( &p_sys->download.lock_wait );
p_sys->download.next_chunk_offset = init_ck->size;
chunk_t *video_chunk = vlc_array_item_at_index( vsms->chunks, 0 );
chunk_t *audio_chunk = vlc_array_item_at_index( asms->chunks, 0 );
if( !video_chunk || !audio_chunk )
goto cancel;
/* Sometimes, the video stream is cut into pieces of one exact length,
* while the audio stream fragments can't be made to match exactly,
* and for some reason the n^th advertised video fragment is related to
* the n+1^th advertised audio chunk or vice versa */
int64_t amid = audio_chunk->duration / 2;
int64_t vmid = video_chunk->duration / 2;
if( audio_chunk->start_time > video_chunk->start_time + vmid )
{
video_chunk = vlc_array_item_at_index( vsms->chunks, 1 );
}
else if ( video_chunk->start_time > audio_chunk->start_time + amid )
{
audio_chunk = vlc_array_item_at_index( asms->chunks, 1 );
}
if( p_sys->b_live )
{
p_sys->download.vlead = video_chunk->start_time + p_sys->timescale / 1000;
p_sys->download.alead = audio_chunk->start_time + p_sys->timescale / 1000;
}
if( Download( s, vsms ) != VLC_SUCCESS )
{
goto cancel;
}
if( Download( s, asms ) != VLC_SUCCESS )
{
goto cancel;
}
int64_t lead = 0;
while( 1 )
{
/* XXX replace magic number 20 by a value depending on
* LookAheadFragmentCount and DVRWindowLength */
vlc_mutex_lock( &p_sys->download.lock_wait );
if( p_sys->b_close )
{
vlc_mutex_unlock( &p_sys->download.lock_wait );
break;
}
lead = __MIN( p_sys->download.vlead, p_sys->download.alead )
- p_sys->playback.toffset;
while( (lead > 10 * p_sys->timescale + video_chunk->start_time) ||
/* If there is no new chunk to process, we wait */
(
!p_sys->b_live &&
p_sys->download.aindex >= (asms->vod_chunks_nb -1) &&
p_sys->download.vindex >= (vsms->vod_chunks_nb - 1)
)
)
{
#if 0
msg_Info( s, "sms_Thread is waiting!" );
msg_Info( s, "toffset is %"PRIu64" vlead is %"PRIu64", alead is %"PRIu64", "\
"and lead is %"PRIi64,
p_sys->playback.toffset,
p_sys->download.vlead - video_chunk->start_time,
p_sys->download.alead - video_chunk->start_time,
lead );
#endif
vlc_cond_wait( &p_sys->download.wait, &p_sys->download.lock_wait );
lead = __MIN( p_sys->download.vlead, p_sys->download.alead )
- p_sys->playback.toffset;
if( p_sys->b_close )
break;
}
if( p_sys->b_tseek )
{
int count = vlc_array_count( p_sys->download.chunks );
chunk_t *ck = NULL;
for( int i = 0; i < count; i++ )
{
ck = vlc_array_item_at_index( p_sys->download.chunks, i );
if( unlikely( !ck ) )
goto cancel;
ck->read_pos = 0;
if( ck->data == NULL )
continue;
block_Release( ck->data );
ck->data = NULL;
}
vlc_array_destroy( p_sys->download.chunks );
p_sys->download.chunks = vlc_array_new();
p_sys->playback.toffset = p_sys->time_pos;
p_sys->download.vlead = p_sys->download.alead = p_sys->time_pos;
p_sys->download.aindex = p_sys->download.vindex = 0;
p_sys->download.next_chunk_offset = 0;
p_sys->playback.boffset = 0;
p_sys->playback.index = 0;
chunk_t *new_init_ck = build_init_chunk( s );
if( !new_init_ck )
goto cancel;
new_init_ck->offset = p_sys->download.next_chunk_offset;
p_sys->download.next_chunk_offset += new_init_ck->size;
vlc_array_append( p_sys->download.chunks, new_init_ck );
p_sys->b_tseek = false;
}
vlc_mutex_unlock( &p_sys->download.lock_wait );
if( p_sys->download.alead < p_sys->download.vlead )
{
if( Download( s, asms ) != VLC_SUCCESS )
break;
}
else if( p_sys->download.vlead <= p_sys->download.alead )
{
if( Download( s, vsms ) != VLC_SUCCESS )
break;
}
}
cancel:
p_sys->b_error = true;
msg_Warn(s, "Canceling download thread!");
vlc_restorecancel( canc );
return NULL;
}
/*****************************************************************************
* smooth.c: Smooth Streaming stream filter
*****************************************************************************
* Copyright (C) 1996-2012 VLC authors and VideoLAN
* $Id$
*
* Author: Frédéric Yhuel <fyhuel _AT_ viotech _DOT_ net>
* Heavily inspired by HLS module of Jean-Paul Saman
* <jpsaman _AT_ videolan _DOT_ org>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*****************************************************************************/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include <limits.h>
#include <vlc_common.h>
#include <vlc_plugin.h>
#include <assert.h>
#include <inttypes.h>
#include <vlc_xml.h>
#include <vlc_charset.h>
#include <vlc_stream.h>
#include <vlc_es.h>
#include <vlc_codecs.h>
#include "smooth.h"
#include "../../demux/mp4/libmp4.h"
/* I make the assumption that when the demux want to do a *time* seek,
* then p_sys->download->boffset > FAKE_STREAM_SIZE, and thus FAKE_STREAM_SIZE
* should be small enough. 1000 seems to be a sensible choice. See also
* chunk_Seek() comments to understand properly */
#define FAKE_STREAM_SIZE 1000
/*****************************************************************************
* Module descriptor
*****************************************************************************/
static int Open( vlc_object_t * );
static void Close( vlc_object_t * );
vlc_module_begin()
set_category( CAT_INPUT )
set_subcategory( SUBCAT_INPUT_STREAM_FILTER )
set_description( N_("Smooth Streaming") )
set_shortname( "Smooth Streaming")
add_shortcut( "smooth" )
set_capability( "stream_filter", 30 )
set_callbacks( Open, Close )
vlc_module_end()
static int Read( stream_t *, void *, unsigned );
static int Peek( stream_t *, const uint8_t **, unsigned );
static int Control( stream_t *, int , va_list );
static bool isSmoothStreaming( stream_t *s )
{
const uint8_t *peek;
const char *needle = "<SmoothStreamingMedia";
const char *encoding = NULL;
bool ret = false;
int i_size = stream_Peek( s->p_source, &peek, 512 );
if( i_size < 512 )
return false;
char *peeked = malloc( 512 );
if( unlikely( !peeked ) )
return false;
memcpy( peeked, peek, 512 );
peeked[511] = peeked[510] = '\0';
if( strstr( (const char *)peeked, needle ) != NULL )
ret = true;
else
/* maybe it's utf-16 encoding, should we also test other encodings? */
{
if( !memcmp( peeked, "\xFF\xFE", 2 ) )
encoding = "UTF-16LE";
else if( !memcmp( peeked, "\xFE\xFF", 2 ) )
encoding = "UTF-16BE";
else
{
free( peeked );
return false;
}
peeked = FromCharset( encoding, peeked, 512 );
if( strstr( peeked, needle ) != NULL )
ret = true;
}
free( peeked );
return ret;
}
#if 0
static void print_chunk( stream_t *s, chunk_t *ck )
{
msg_Info( s, "chunk %u type %i: duration is %"PRIu64", stime is %"PRIu64", "\
"size is %i, offset is %"PRIu64", read_pos is %i.",
ck->sequence, ck->type, ck->duration,
ck->start_time, ck->size, ck->offset, ck->read_pos );
}
#endif
static int parse_Manifest( stream_t *s )
{
stream_sys_t *p_sys = s->p_sys;
xml_t *vlc_xml = NULL;
xml_reader_t *vlc_reader = NULL;
int type = UNKNOWN_ES;
const char *name, *value;
stream_t *st = s->p_source;
msg_Dbg( s, "Manifest parsing\n" );
vlc_xml = xml_Create( st );
if( !vlc_xml )
{
msg_Err( s, "Failed to open XML parser" );
return VLC_EGENERIC;
}
vlc_reader = xml_ReaderCreate( vlc_xml, st );
if( !vlc_reader )
{
msg_Err( s, "Failed to open source for parsing" );
xml_Delete( vlc_xml );
return VLC_EGENERIC;
}
const char *node;
char *stream_name = NULL;
uint8_t *WaveFormatEx;
int stream_type = UNKNOWN_ES;
sms_stream_t *sms = NULL;
quality_level_t *ql = NULL;
int64_t start_time = 0, duration = 0;
int64_t computed_start_time = 0, computed_duration = 0;
unsigned next_track_id = 1;
unsigned next_qid = 1;
int loop_count = 0;
bool b_weird = false;
#define TIMESCALE 10000000
while( (type = xml_ReaderNextNode( vlc_reader, &node )) > 0 )
{
switch( type )
{
case XML_READER_STARTELEM:
if( !strcmp( node, "SmoothStreamingMedia" ) )
{
while( (name = xml_ReaderNextAttr( vlc_reader, &value )) )
{
if( !strcmp( name, "Duration" ) )
p_sys->vod_duration = strtoull( value, NULL, 10 );
if( !strcmp( name, "TimeScale" ) )
p_sys->timescale = strtoull( value, NULL, 10 );
}
if( !p_sys->timescale )
p_sys->timescale = TIMESCALE;
}
if( !strcmp( node, "StreamIndex" ) )
{
sms = sms_New();
if( unlikely( !sms ) )
return VLC_ENOMEM;
sms->id = next_track_id;
next_track_id++;
while( (name = xml_ReaderNextAttr( vlc_reader, &value )) )
{
if( !strcmp( name, "Type" ) )
{
if( !strcmp( value, "video" ) )
stream_type = VIDEO_ES;
else if( !strcmp( value, "audio" ) )
stream_type = AUDIO_ES;
else if( !strcmp( value, "text" ) )
stream_type = SPU_ES;
}
if( !strcmp( name, "Name" ) )
stream_name = strdup( value );
if( !strcmp( name, "TimeScale" ) )
sms->timescale = strtoull( value, NULL, 10 );
if( !strcmp( name, "Chunks" ) )
{
sms->vod_chunks_nb = strtol( value, NULL, 10 );
if( sms->vod_chunks_nb == 0 ) /* live */
sms->vod_chunks_nb = UINT32_MAX;
}
if( !strcmp( name, "QualityLevels" ) )
sms->qlevel_nb = strtoul( value, NULL, 10 );
if( !strcmp( name, "Url" ) )
sms->url_template = strdup(value);
}
if( sms && !sms->timescale )
sms->timescale = TIMESCALE;
if( !stream_name )
{
if( stream_type == VIDEO_ES )
stream_name = strdup( "video" );
else if( stream_type == AUDIO_ES )
stream_name = strdup( "audio" );
else if( stream_type == SPU_ES )
stream_name = strdup( "text" );
}
sms->name = stream_name;
sms->type = stream_type;
vlc_array_append( p_sys->sms_streams, sms );
}
if( !strcmp( node, "QualityLevel" ) )
{
ql = ql_New();
if( !ql )
return VLC_ENOMEM;
ql->id = next_qid;
next_qid++;
while( (name = xml_ReaderNextAttr( vlc_reader, &value )) )
{
if( !strcmp( name, "Index" ) )
ql->Index = strtol( value, NULL, 10 );
if( !strcmp( name, "Bitrate" ) )
ql->Bitrate = strtoull( value, NULL, 10 );
if( !strcmp( name, "FourCC" ) )
ql->FourCC = VLC_FOURCC( value[0], value[1],
value[2], value[3] );
if( !strcmp( name, "CodecPrivateData" ) )
ql->CodecPrivateData = strdup( value );
if( !strcmp( name, "WaveFormatEx" ) )
{
WaveFormatEx = decode_string_hex_to_binary( value );
uint16_t data_len = ((uint16_t *)WaveFormatEx)[8];
ql->CodecPrivateData = strndup( value + 36, data_len * 2 );
uint16_t wf_tag = ((uint16_t *)WaveFormatEx)[0];
wf_tag_to_fourcc( wf_tag, &ql->FourCC, NULL );
ql->Channels = ((uint16_t *)WaveFormatEx)[1];
ql->SamplingRate = ((uint32_t *)WaveFormatEx)[1];
ql->AvgBytesPerSec = ((uint32_t *)WaveFormatEx)[2];
ql->nBlockAlign = ((uint16_t *)WaveFormatEx)[6];
ql->BitsPerSample = ((uint16_t *)WaveFormatEx)[7];
free( WaveFormatEx );
}
if( !strcmp( name, "MaxWidth" ) || !strcmp( name, "Width" ) )
ql->MaxWidth = strtoul( value, NULL, 10 );
if( !strcmp( name, "MaxHeight" ) || !strcmp( name, "Height" ) )
ql->MaxHeight = strtoul( value, NULL, 10 );
if( !strcmp( name, "Channels" ) )
ql->Channels = strtoul( value, NULL, 10 );
if( !strcmp( name, "SamplingRate" ) )
ql->SamplingRate = strtoul( value, NULL, 10 );
if( !strcmp( name, "BitsPerSample" ) )
ql->BitsPerSample = strtoul( value, NULL, 10 );
}
vlc_array_append( sms->qlevels, ql );
}
if( !strcmp( node, "c" ) )
{
loop_count++;
start_time = duration = -1;
while( (name = xml_ReaderNextAttr( vlc_reader, &value )) )
{
if( !strcmp( name, "t" ) )
start_time = strtoull( value, NULL, 10 );
if( !strcmp( name, "d" ) )
duration = strtoull( value, NULL, 10 );
}
if( start_time == -1 )
{
assert( duration != -1 );
computed_start_time += computed_duration;
computed_duration = duration;
}
else if( duration == -1 )
{
assert( start_time != -1 );
/* Handle weird Manifests which give only the start time
* of the first segment. In those cases, we have to look
* at the start time of the second segment to compute
* the duration of the first one. */
if( loop_count == 1 )
{
b_weird = true;
computed_start_time = start_time;
continue;
}
computed_duration = start_time - computed_start_time;
if( !b_weird )
computed_start_time = start_time;
}
else
{
if( b_weird )
computed_duration = start_time - computed_start_time;
else
{
computed_start_time = start_time;
computed_duration = duration;
}
}
if( unlikely( chunk_New( sms, computed_duration,
computed_start_time ) == NULL ) )
{
return VLC_ENOMEM;
}
if( b_weird && start_time != -1 )
computed_start_time = start_time;
}
break;
case XML_READER_ENDELEM:
if( strcmp( node, "StreamIndex" ) )
break;
stream_name = NULL;
stream_type = UNKNOWN_ES;
computed_start_time = 0;
computed_duration = 0;
loop_count = 0;
if( b_weird && !chunk_New( sms, computed_duration, computed_start_time ) )
return VLC_ENOMEM;
b_weird = false;
next_qid = 1;
if( sms->qlevel_nb == 0 )
sms->qlevel_nb = vlc_array_count( sms->qlevels );
break;
case XML_READER_NONE:
break;
case XML_READER_TEXT:
break;
default:
return VLC_EGENERIC;
}
}
#undef TIMESCALE
xml_ReaderDelete( vlc_reader );
xml_Delete( vlc_xml );
return VLC_SUCCESS;
}
static int Open( vlc_object_t *p_this )
{
stream_t *s = (stream_t*)p_this;
stream_sys_t *p_sys;
if( !isSmoothStreaming( s ) )
return VLC_EGENERIC;
msg_Info( p_this, "Smooth Streaming (%s)", s->psz_path );
s->p_sys = p_sys = calloc( 1, sizeof(*p_sys ) );
if( unlikely( p_sys == NULL ) )
return VLC_ENOMEM;
char *uri = NULL;
if( unlikely( asprintf( &uri, "%s://%s", s->psz_access, s->psz_path ) < 0 ) )
{
free( p_sys );
return VLC_ENOMEM;
}
/* remove the last part of the url */
char *pos = strrchr( uri, '/');
*pos = '\0';
p_sys->base_url = uri;
/* XXX I don't know wether or not we should allow caching */
p_sys->b_cache = false;
p_sys->sms_streams = vlc_array_new();
p_sys->download.chunks = vlc_array_new();
if( unlikely( !p_sys->sms_streams || !p_sys->download.chunks ) )
{
free( p_sys );
return VLC_ENOMEM;
}
/* Parse SMS ismc content. */
if( parse_Manifest( s ) != VLC_SUCCESS )
{
free( p_sys );
return VLC_EGENERIC;
}
if( !p_sys->vod_duration )
p_sys->b_live = true;
p_sys->i_tracks = vlc_array_count( p_sys->sms_streams );
/* FIXME */
p_sys->i_selected_tracks = 2; /* one video track and one audio track */
/* Choose first video stream available */
sms_stream_t *vsms = NULL;
for( unsigned i = 0; i < p_sys->i_tracks; i++ )
{
vsms = vlc_array_item_at_index( p_sys->sms_streams, i );
if( vsms->type == VIDEO_ES )
{
msg_Dbg( s, "Video stream chosen is %s", vsms->name );
break;
}
}
p_sys->vstream = vsms;
/* Choose first audio stream available */
sms_stream_t *asms = NULL;
for( unsigned i = 0; i < p_sys->i_tracks; i++ )
{
asms = vlc_array_item_at_index( p_sys->sms_streams, i );
//if( asms->type == AUDIO_ES && !strcmp( asms->name, "audio_eng" ) )
if( asms->type == AUDIO_ES )
{
msg_Dbg( s, "Audio stream chosen is %s", asms->name );
break;
}
}
p_sys->astream = asms;
/* Choose lowest quality for the first chunks */
quality_level_t *wanted, *qlvl;
sms_stream_t *sms = NULL;
for( unsigned i = 0; i < p_sys->i_tracks; i++ )
{
wanted = qlvl = NULL;
sms = vlc_array_item_at_index( p_sys->sms_streams, i );
wanted = vlc_array_item_at_index( sms->qlevels, 0 );
for( unsigned i=1; i < sms->qlevel_nb; i++ )
{
qlvl = vlc_array_item_at_index( sms->qlevels, i );
if( qlvl->Bitrate < wanted->Bitrate )
wanted = qlvl;
}
sms->download_qlvl = wanted->id;
}
vlc_mutex_init( &p_sys->download.lock_wait );
vlc_cond_init( &p_sys->download.wait );
/* */
s->pf_read = Read;
s->pf_peek = Peek;
s->pf_control = Control;
if( vlc_clone( &p_sys->thread, sms_Thread, s, VLC_THREAD_PRIORITY_INPUT ) )
{
free( p_sys );
vlc_mutex_destroy( &p_sys->download.lock_wait );
vlc_cond_destroy( &p_sys->download.wait );
return VLC_EGENERIC;
}
return VLC_SUCCESS;
}
static void Close( vlc_object_t *p_this )
{
stream_t *s = (stream_t*)p_this;
stream_sys_t *p_sys = s->p_sys;
vlc_mutex_lock( &p_sys->download.lock_wait );
p_sys->b_close = true;
/* Negate the condition variable's predicate */
p_sys->download.vlead = p_sys->download.alead = 0;
p_sys->playback.toffset = 0;
vlc_cond_signal(&p_sys->download.wait);
vlc_mutex_unlock( &p_sys->download.lock_wait );
vlc_join( p_sys->thread, NULL );
vlc_mutex_destroy( &p_sys->download.lock_wait );
vlc_cond_destroy( &p_sys->download.wait );
/* Free sms streams */
sms_stream_t *sms;
for( int i = 0; i < vlc_array_count( p_sys->sms_streams ); i++ )
{
sms = vlc_array_item_at_index( p_sys->sms_streams, i );
if( sms)
sms_Free( sms );
}
vlc_array_destroy( p_sys->sms_streams );
vlc_array_destroy( p_sys->download.chunks );
free( p_sys->base_url );
free( p_sys );
}
static chunk_t *get_chunk( stream_t *s, const bool wait )
{
stream_sys_t *p_sys = s->p_sys;
unsigned count;
chunk_t *chunk = NULL;
vlc_mutex_lock( &p_sys->download.lock_wait );
count = vlc_array_count( p_sys->download.chunks );
while( p_sys->playback.index >= count || p_sys->b_tseek )
{
/* Yes I know, checking for p_sys->b_die is not reliable,
* that's why vlc_object_alive() has been deprecated. But if I
* understood well, there is no good solution with a stream_filter
* module anyaway. */
if( !wait || s->b_die || p_sys->b_error )
{
vlc_mutex_unlock( &p_sys->download.lock_wait );
msg_Warn( s, "get_chunk failed! (playback index %u)",
p_sys->playback.index );
return NULL;
}
if( !p_sys->b_live &&
p_sys->download.aindex >= (p_sys->vstream->vod_chunks_nb -1) &&
p_sys->download.vindex >= (p_sys->astream->vod_chunks_nb -1) )
{
vlc_mutex_unlock( &p_sys->download.lock_wait );
msg_Info( s, "No more chunks, end of the VOD" );
return NULL;
}
msg_Dbg( s, "get_chunk is waiting !!!" );
vlc_cond_timedwait( &p_sys->download.wait,
&p_sys->download.lock_wait, mdate() + 500000 );
count = vlc_array_count( p_sys->download.chunks );
msg_Dbg( s, "count is %u, and index is %u", count, p_sys->playback.index );
}
chunk = vlc_array_item_at_index( p_sys->download.chunks, p_sys->playback.index );
vlc_mutex_unlock( &p_sys->download.lock_wait );
return chunk;
}
static int sms_Read( stream_t *s, uint8_t *p_read, int i_read )
{
stream_sys_t *p_sys = s->p_sys;
int copied = 0;
chunk_t *chunk = NULL;
do
{
chunk = get_chunk( s, true );
if( !chunk )
return copied;
if( chunk->read_pos >= (int)chunk->size )
{
if( chunk->type == VIDEO_ES )
{
vlc_mutex_lock( &p_sys->download.lock_wait );
p_sys->playback.toffset += chunk->duration;
vlc_mutex_unlock( &p_sys->download.lock_wait );
vlc_cond_signal( &p_sys->download.wait);
}
if( !p_sys->b_cache || p_sys->b_live )
{
block_Release( chunk->data );
chunk->data = NULL;
}
chunk->read_pos = 0;
p_sys->playback.index += 1;
msg_Dbg( s, "Incrementing playback index" );
continue;
}
if( chunk->read_pos == 0 )
{
const char *verb = p_read == NULL ? "skipping" : "reading";
msg_Dbg( s, "%s chunk %u (%u bytes), type %i",
verb, chunk->sequence, i_read, chunk->type );
/* check integrity */
uint32_t type;
uint8_t *slice = chunk->data->p_buffer;
SMS_GET4BYTES( type );
SMS_GETFOURCC( type );
assert( type == ATOM_moof || type == ATOM_uuid );
}
int len = -1;
uint8_t *src = chunk->data->p_buffer + chunk->read_pos;
if( i_read <= chunk->size - chunk->read_pos )
len = i_read;
else
len = chunk->size - chunk->read_pos;
if( len > 0 )
{
if( p_read ) /* otherwise caller skips data */
memcpy( p_read + copied, src, len );
chunk->read_pos += len;
copied += len;
i_read -= len;
}
} while ( i_read > 0 );
return copied;
}
static int Read( stream_t *s, void *buffer, unsigned i_read )
{
stream_sys_t *p_sys = s->p_sys;
int length = 0;
if( p_sys->b_error )
return 0;
length = sms_Read( s, (uint8_t*) buffer, i_read );
if( length < 0 )
return 0;
/* This call to sms_Read will increment p_sys->playback.index
* in case the last chunk we read into is entirely read */
sms_Read( s, NULL, 0 );
p_sys->playback.boffset += length;
if( (unsigned)length < i_read )
msg_Warn( s, "could not read %i bytes, only %i!", i_read, length );
return length;
}
/* The MP4 demux should never have to to peek outside the current chunk */
static int Peek( stream_t *s, const uint8_t **pp_peek, unsigned i_peek )
{
chunk_t *chunk = get_chunk( s, true );
if( !chunk || !chunk->data )
return 0;
int bytes = chunk->size - chunk->read_pos;
assert( bytes > 0 );
if( (unsigned)bytes < i_peek )
{
msg_Err( s, "could not peek %u bytes, only %i!", i_peek, bytes );
}
msg_Dbg( s, "peeking at chunk %u!", chunk->sequence );
*pp_peek = chunk->data->p_buffer + chunk->read_pos;
return bytes;
}
/* Normaly a stream_filter is not able to provide *time* seeking, since a
* stream_filter operates on a byte stream. Thus, in order to circumvent this
* limitation, I treat a STREAM_SET_POSITION request which value "pos" is less
* than FAKE_STREAM_SIZE as a *time* seek request, and more precisely a request
* to jump at time position: pos / FAKE_STREAM_SIZE * total_video_duration.
* For exemple, it pos == 500, it would be interpreted as a request to jump at
* the middle of the video.
* If pos > 1000, it would be treated as a normal byte seek request. That means
* the demux is not able to request a byte seek with 0 <= pos <= 1000
* (unless it is in the current chunk), but that doesn't matter in practice.
* Of course this a bit hack-ish, but if Smooth Streaming doesn't die, its
* implementation will be moved to a access_demux module, and this hack won't
* be needed anymore (among others). */
static int chunk_Seek( stream_t *s, const uint64_t pos )
{
stream_sys_t *p_sys = s->p_sys;
if( pos == p_sys->playback.boffset )
return VLC_SUCCESS;
chunk_t *chunk = get_chunk( s, false );
if( chunk == NULL )
return VLC_EGENERIC;
bool inside_chunk = pos >= chunk->offset &&
pos < (chunk->offset + chunk->size) ? true : false;
if( inside_chunk )
{
chunk->read_pos = pos - chunk->offset;
p_sys->playback.boffset = pos;
return VLC_SUCCESS;
}
else
{
if( p_sys->b_live )
{
msg_Err( s, "Cannot seek outside the current chunk for a live stream" );
return VLC_EGENERIC;
}
msg_Info( s, "Seeking outside the current chunk" );
assert( pos <= FAKE_STREAM_SIZE );
vlc_mutex_lock( &p_sys->download.lock_wait );
p_sys->b_tseek = true;
p_sys->time_pos = p_sys->vod_duration * pos / FAKE_STREAM_SIZE;
p_sys->download.vlead = p_sys->download.alead = 0;
p_sys->playback.toffset = 0;
vlc_cond_signal( &p_sys->download.wait);
vlc_mutex_unlock( &p_sys->download.lock_wait );
return VLC_SUCCESS;
}
}
static int Control( stream_t *s, int i_query, va_list args )
{
stream_sys_t *p_sys = s->p_sys;
switch( i_query )
{
case STREAM_CAN_SEEK:
*(va_arg( args, bool * )) = true;
break;
case STREAM_CAN_FASTSEEK:
*(va_arg( args, bool * )) = false;
break;
case STREAM_GET_POSITION:
*(va_arg( args, uint64_t * )) = p_sys->playback.boffset;
break;
case STREAM_SET_POSITION:
{
uint64_t pos = (uint64_t)va_arg(args, uint64_t);
int ret = chunk_Seek(s, pos);
if( ret == VLC_SUCCESS )
break;
else
return VLC_EGENERIC;
}
case STREAM_GET_SIZE:
*(va_arg( args, uint64_t * )) = FAKE_STREAM_SIZE;
break;
default:
return VLC_EGENERIC;
}
return VLC_SUCCESS;
}
/*****************************************************************************
* smooth.h: misc. stuff
*****************************************************************************
* Copyright (C) 1996-2012 VLC authors and VideoLAN
* $Id$
*
* Author: Frédéric Yhuel <fyhuel _AT_ viotech _DOT_ net>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*****************************************************************************/
#ifndef _VLC_SMOOTH_H
#define _VLC_SMOOTH_H 1
//#define DISABLE_BANDWIDTH_ADAPTATION
typedef struct item_s
{
uint64_t value;
struct item_s *next;
} item_t;
typedef struct sms_queue_s
{
int length;
item_t *first;
} sms_queue_t;
typedef struct chunk_s
{
int64_t duration; /* chunk duration (seconds / TimeScale) */
int64_t start_time; /* PTS (seconds / TimeScale) */
int size; /* chunk size in bytes */
unsigned sequence; /* unique sequence number */
uint64_t offset; /* offset in the media */
int read_pos; /* position in the chunk */
int type; /* video, audio, or subtitles */
block_t *data;
} chunk_t;
typedef struct quality_level_s
{
int Index;
uint32_t FourCC;
unsigned Bitrate;
unsigned MaxWidth;
unsigned MaxHeight;
unsigned SamplingRate;
unsigned AvgBytesPerSec;
unsigned Channels;
unsigned BitsPerSample;
unsigned PacketSize;
unsigned AudioTag;
unsigned nBlockAlign;
unsigned id;
char *CodecPrivateData; /* hex encoded string */
} quality_level_t;
typedef struct sms_stream_s
{
vlc_array_t *qlevels; /* list of available Quality Levels */
vlc_array_t *chunks; /* list of chunks */
unsigned vod_chunks_nb; /* total num of chunks of the VOD stream */
unsigned timescale;
unsigned qlevel_nb; /* number of quality levels */
unsigned id; /* track id, will be set arbitrarily */
char *name;
char *url_template;
int type;
unsigned download_qlvl; /* current quality level ID for Download() */
} sms_stream_t;
struct stream_sys_t
{
char *base_url; /* URL common part for chunks */
vlc_thread_t thread; /* SMS chunk download thread */
vlc_array_t *sms_streams; /* array of sms_stream_t */
sms_stream_t *vstream; /* current video stream */
sms_stream_t *astream; /* current audio stream */
sms_stream_t *tstream; /* current text stream */
unsigned i_tracks; /* Total number of tracks in the Manifest */
unsigned i_selected_tracks;
sms_queue_t *bws; /* Measured bandwidths of the N last chunks */
uint64_t vod_duration; /* total duration of the VOD media */
int64_t time_pos;
unsigned timescale;
/* Download */
struct sms_download_s
{
uint64_t alead; // how much audio/video/text data is
uint64_t vlead; // available (downloaded),
uint64_t tlead; // in seconds / TimeScale
unsigned aindex; /* current audio chunk for download */
unsigned vindex; /* video */
unsigned sindex; /* spu */
uint64_t next_chunk_offset;
vlc_array_t *chunks; /* chunks that have been downloaded */
vlc_mutex_t lock_wait; /* protect chunk download counter. */
vlc_cond_t wait; /* some condition to wait on */
} download;
/* Playback */
struct sms_playback_s
{
uint64_t boffset; /* current byte offset in media */
uint64_t toffset; /* current time offset in media */
unsigned index; /* current chunk for playback */
} playback;
/* state */
bool b_cache; /* can cache files */
bool b_live; /* live stream? or vod? */
bool b_error; /* parsing error */
bool b_close; /* set by Close() */
bool b_tseek; /* time seeking */
};
#define SMS_GET4BYTES( dst ) do { \
dst = U32_AT( slice ); \
slice += 4; \
} while(0)
#define SMS_GET1BYTE( dst ) do { \
dst = *slice; \
slice += 1; \
} while(0)
#define SMS_GET3BYTES( dst ) do { \
dst = Get24bBE( slice ); \
slice += 3; \
} while(0)
#define SMS_GET8BYTES( dst ) do { \
dst = U64_AT( slice ); \
slice += 8; \
} while(0)
#define SMS_GET4or8BYTES( dst ) \
if( (version) == 0 ) \
SMS_GET4BYTES( dst ); \
else \
SMS_GET8BYTES( dst ); \
#define SMS_GETFOURCC( dst ) do { \
memcpy( &dst, slice, 4 ); \
slice += 4; \
} while(0)
sms_queue_t *sms_queue_init( const int );
int sms_queue_put( sms_queue_t *, const uint64_t );
uint64_t sms_queue_avg( sms_queue_t *);
quality_level_t *get_qlevel( sms_stream_t *, const unsigned );
void* sms_Thread( void *);
quality_level_t * ql_New( void );
void ql_Free( quality_level_t *);
chunk_t *chunk_New( sms_stream_t* , uint64_t , uint64_t );
void chunk_Free( chunk_t *);
sms_stream_t * sms_New( void );
void sms_Free( sms_stream_t *);
uint8_t *decode_string_hex_to_binary( const char * );
#endif
/*****************************************************************************
* utils.c: misc. stuff
*****************************************************************************
* Copyright (C) 1996-2012 VLC authors and VideoLAN
* $Id$
*
* Author: Frédéric Yhuel <fyhuel _AT_ viotech _DOT_ net>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*****************************************************************************/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include <vlc_common.h>
#include <vlc_es.h>
#include <vlc_block.h>
#include <assert.h>
#include "smooth.h"
static int hex_digit( const char c )
{
if (c >= 'A' && c <= 'F')
return c - 'A' + 10;
else if (c >= 'a' && c <= 'f')
return c - 'a' + 10;
else if (c >= '0' && c<= '9')
return c - '0';
else
return -1;
}
uint8_t *decode_string_hex_to_binary( const char *psz_src )
{
int i = 0, j = 0, first_digit, second_digit;
int i_len = strlen( psz_src );
assert( i_len % 2 == 0 );
uint8_t *p_data = malloc( i_len / 2 );
if( !p_data )
return NULL;
while( i < i_len )
{
first_digit = hex_digit( psz_src[i++] );
second_digit = hex_digit( psz_src[i++] );
assert( first_digit >= 0 && second_digit >= 0 );
p_data[j++] = ( first_digit << 4 ) | second_digit;
}
return p_data;
}
quality_level_t * ql_New( void )
{
quality_level_t *ql = calloc( 1, sizeof( quality_level_t ) );
if( unlikely( !ql ) ) return NULL;
ql->Index = -1;
return ql;
}
void ql_Free( quality_level_t *qlevel )
{
free( qlevel->CodecPrivateData );
free( qlevel );
qlevel = NULL;
}
chunk_t *chunk_New( sms_stream_t* sms, uint64_t duration,\
uint64_t start_time )
{
chunk_t *chunk = calloc( 1, sizeof( chunk_t ) );
if( unlikely( chunk == NULL ) )
return NULL;
chunk->duration = duration;
chunk->start_time = start_time;
chunk->type = UNKNOWN_ES;
chunk->sequence = vlc_array_count( sms->chunks );
vlc_array_append( sms->chunks, chunk );
return chunk;
}
void chunk_Free( chunk_t *chunk )
{
if( chunk->data )
block_Release( chunk->data );
free( chunk );
chunk = NULL;
}
sms_stream_t * sms_New( void )
{
sms_stream_t *sms = calloc( 1, sizeof( sms_stream_t ) );
if( unlikely( !sms ) ) return NULL;
sms->qlevels = vlc_array_new();
sms->chunks = vlc_array_new();
sms->type = UNKNOWN_ES;
return sms;
}
void sms_Free( sms_stream_t *sms )
{
if( sms->qlevels )
{
for( int n = 0; n < vlc_array_count( sms->qlevels ); n++ )
{
quality_level_t *qlevel = vlc_array_item_at_index( sms->qlevels, n );
if( qlevel ) ql_Free( qlevel );
}
vlc_array_destroy( sms->qlevels );
}
if( sms->chunks )
{
for( int n = 0; n < vlc_array_count( sms->chunks ); n++ )
{
chunk_t *chunk = vlc_array_item_at_index( sms->chunks, n );
if( chunk) chunk_Free( chunk );
}
vlc_array_destroy( sms->chunks );
}
free( sms );
sms = NULL;
}
quality_level_t *get_qlevel( sms_stream_t *sms, const unsigned qid )
{
quality_level_t *qlevel = NULL;
for( unsigned i = 0; i < sms->qlevel_nb; i++ )
{
qlevel = vlc_array_item_at_index( sms->qlevels, i );
if( qlevel->id == qid )
return qlevel;
}
return NULL;
}
sms_queue_t *sms_queue_init( const int length )
{
sms_queue_t *ret = malloc( sizeof( sms_queue_t ) );
if( unlikely( !ret ) )
return NULL;
ret->length = length;
ret->first = NULL;
return ret;
}
int sms_queue_put( sms_queue_t *queue, const uint64_t value )
{
item_t *last = queue->first;
int i = 0;
for( i = 0; i < queue->length - 1; i++ )
{
if( last )
last = last->next;
}
if( i == queue->length - 1 )
FREENULL( last );
item_t *new = malloc( sizeof( item_t ) );
if( unlikely( !new ) )
return VLC_ENOMEM;
new->value = value;
new->next = queue->first;
queue->first = new;
return VLC_SUCCESS;
}
uint64_t sms_queue_avg( sms_queue_t *queue )
{
item_t *last = queue->first;
if( last == NULL )
return 0;
uint64_t sum = queue->first->value;
for( int i = 0; i < queue->length - 1; i++ )
{
if( last )
{
last = last->next;
if( last )
sum += last->value;
}
}
return sum / queue->length;
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment