HLS is an awesome streaming protocol. Forget Dash, forget HDS, HLS is probably the most supported among many vendors. Android 4.X fully supports this protocol, even though there are some minor issues when changing from different bitrates.
The following scripts were tested using an iPhone 4 with iOS 7, and an Android 4.1. They both work fine, but iPhone is way better in terms of its adaptive algorithm.
The input stream was H264 Main with 720×576 resolution. Audio was aac_latm. I tried to remux into different chunks without reencoding but it did not work. I had to encode both Audio and Video to properly play in iOS.
I used an Ubuntu 11.10 64 bit version, with FFMpeg 2.1.3, along with a 4 core Intel Xeon E3-1220. Four bitrates (256, 512, 1024, 1436) with veryfast preset occupies roughly 2 cores. For better margins you can use superfast or ultrafast preset.
processor : 3
vendor_id : GenuineIntel
cpu family : 6
model : 42
model name : Intel(R) Xeon(R) CPU E31220 @ 3.10GHz
stepping : 7
cpu MHz : 3092.956
cache size : 8192 KB
INPUT:
Input #0, mpegts, from ‘udp://239.192.1.1:1234’:
Duration: N/A, start: 52236.297844, bitrate: N/A
Program 1101
Stream #0:0[0x20](por): Subtitle: dvb_teletext ([6][0][0][0] / 0x0006)
Stream #0:1[0x100]: Video: h264 (Main) ([27][0][0][0] / 0x001B), yuv420p(tv, bt470bg), 720×576 [SAR 16:11 DAR 20:11], 25 fps, 50 tbr, 90k tbn, 50 tbc
Stream #0:2[0x101](por): Audio: aac_latm ([17][0][0][0] / 0x0011), 48000 Hz, stereo, fltp
Stream #0:3[0x102](por): Audio: aac_latm ([17][0][0][0] / 0x0011), 48000 Hz, stereo, fltp (visual impaired)
How to create the HLS streams
This is the source code for the hls daemon. This script launches the FFmpeg daemon with all the parameters required. This hls script is launched by an upstart job.
root@transcoder:/usr/src# cat hls.sh
#!/bin/bash
NAME=$1
CHANNEL=$2
BR=$3
MAXRATE=`expr $3 + 100`
BUFSIZE=`expr $3 + 500`
# Change this to superfast or veryfast for lower computational needs
PRESET="veryfast"
BRPLAYLIST="${BR}_${NAME}"
options=" -vbsf h264_mp4toannexb -flags -global_header -c:v libx264 -b:v ${BR}k -maxrate ${MAXRATE}k -bufsize ${BUFSIZE}k -threads 4 -preset ${PRESET} -profile:v main -flags +cgop -c:a libfaac -b:a 64k -ac 2 "
segmentoptions=" -f segment -segment_list ${BRPLAYLIST}.m3u8 -segment_list_flags -cache+live -segment_time 10 -segment_list_size 5 -segment_format mpegts -copyts "
ffmpeg -y -i udp://${CHANNEL}:1234 -probesize 500000 -map 0:1 -map 0:2 $options $segmentoptions "${BRPLAYLIST}_%05d.ts"
I then placed four upstart files to match the bandwidths I was looking for.
root@transcoder:/home/nonius# ls /etc/init/*rtp*
/etc/init/rtp1024.conf /etc/init/rtp256.conf /etc/init/rtp1436.conf /etc/init/rtp512.conf
Example of one of these upstart jobs.
start on filesystem or runlevel [2345]
stop on runlevel [!2345]
respawn
respawn limit 10 5
umask 022
chdir /var/run/www
exec /usr/src/hls.sh rtp 239.192.1.1 512
OUTPUTS:
Each of the outputs is close to the selected output but still a couple of kbs above. This is due to the maxrate being calculated with input+100, I think. This translates into higher bitrates at certain times. One can perhaps mitigate this by placing the maxrate equal to the bitrate required. But all in all in a long term run, the bitrates come down to the selected ones.
Input #0, mpegts, from ‘1436_rtp_00124.ts’:
Duration: 00:00:10.03, start: 51597.172356, bitrate: 1784 kb/s
Program 1
Metadata:
service_name : Service01
service_provider: FFmpeg
Stream #0:0[0x100]: Video: h264 (Main) ([27][0][0][0] / 0x001B), yuv420p, 720×576 [SAR 16:11 DAR 20:11], 25 fps, 25 tbr, 90k tbn, 50 tbc
Stream #0:1[0x101]: Audio: aac ([15][0][0][0] / 0x000F), 48000 Hz, stereo, fltp, 56 kb/s
Input #0, mpegts, from ‘1024_rtp_00124.ts’:
Duration: 00:00:10.03, start: 51597.172356, bitrate: 1329 kb/s
Program 1
Metadata:
service_name : Service01
service_provider: FFmpeg
Stream #0:0[0x100]: Video: h264 (Main) ([27][0][0][0] / 0x001B), yuv420p, 720×576 [SAR 16:11 DAR 20:11], 25 fps, 25 tbr, 90k tbn, 50 tbc
Stream #0:1[0x101]: Audio: aac ([15][0][0][0] / 0x000F), 48000 Hz, stereo, fltp, 57 kb/s
Input #0, mpegts, from ‘512_rtp_00124.ts’:
Duration: 00:00:10.03, start: 51597.172356, bitrate: 750 kb/s
Program 1
Metadata:
service_name : Service01
service_provider: FFmpeg
Stream #0:0[0x100]: Video: h264 (Main) ([27][0][0][0] / 0x001B), yuv420p, 720×576 [SAR 16:11 DAR 20:11], 25 fps, 25 tbr, 90k tbn, 50 tbc
Stream #0:1[0x101]: Audio: aac ([15][0][0][0] / 0x000F), 48000 Hz, stereo, fltp, 56 kb/s
Input #0, mpegts, from ‘256_rtp_00124.ts’:
Duration: 00:00:10.03, start: 51597.172356, bitrate: 464 kb/s
Program 1
Metadata:
service_name : Service01
service_provider: FFmpeg
Stream #0:0[0x100]: Video: h264 (Main) ([27][0][0][0] / 0x001B), yuv420p, 720×576 [SAR 16:11 DAR 20:11], 25 fps, 25 tbr, 90k tbn, 50 tbc
Stream #0:1[0x101]: Audio: aac ([15][0][0][0] / 0x000F), 48000 Hz, stereo, fltp, 57 kb/s
So how do we create several of these and have them in sync with each other? My trick was to simply use the -copyts. This translates into an almost perfect sync between different bitrates. Obviously all the daemons have to start mostly at the same time, with some ms between them. But for those of you who are more skeptic I’ve attached some of the results of the hole process.
256_rtp_00124.ts
512_rtp_00124.ts
1024_rtp_00124.ts
1436_rtp_00124.ts
The only thing missing is the main adaptive playlist:
root@transcoder:/run/www# cat rtp.m3u8
#EXTM3U
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=256000,RESOLUTION=720x576
256_rtp.m3u8
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=512000,RESOLUTION=720x576
512_rtp.m3u8
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=1024000,RESOLUTION=720x576
1024_rtp.m3u8
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=1536000,RESOLUTION=720x576
1436_rtp.m3u8
Note:
VLC 2.1.3 seems to have some issues with Live event HLS playback. This is due to the fact that it downloads several chunks in a row, plays them and misses the next window in time. I’m not sure why this happens, but iPhone and Android have worked flawless for me expect for some Android glitches.