Developers

Live Translation for Broadcast, Multi-Audio, Multi-Subtitle, and DVB-Subs With Bitmovin’s Live Encoder

. 16 min read

TLDR

  • Bitmovin’s Live Encoder now connects to Syncwords for real time auto-dubbing and subtitling.
  • One live input, multiple audio languages, multiple WebVtt subtitle tracks.
  • Subtitles in production for German, English, French, Italian, Portuguese, and Spanish.
  • Started as a hackathon project, now a working part of Bitmovin’s Live Encoding product.

Multi-language output for a live stream means new audio and subtitle streams, manifest changes to signal them, and a translation and dubbing pipeline running in real time, not a toggle in an encoder config. Picking one language and living with it is the easy path, but it leaves viewers who don’t speak that language choosing between watching without understanding or waiting for a translated recap later. Now the picture is changing, every viewer hearing a dubbed audio track and reading subtitles in their own language, generated automatically as the event happens. No translation booth, no subtitle operators standing by, no delay.

In this blog, I’ll go into why multi-language live streaming is a harder problem than it looks, how we connected Bitmovin’s Live Encoder to Syncwords’ automated translation and dubbing service, and where DVB-subs and DVB-teletext language support stands today.

The problem we were solving

Live content is where language barriers hit hardest. Same-language captioning is common, and in the US and EU it’s often legally required, FCC rules mandate captions for most broadcast television under the CVAA, and the EU’s Audiovisual Media Services Directive pushes member states toward similar obligations. But a live event in a second or third language is a different problem, and one the law generally doesn’t touch.

Viewers who don’t speak the broadcast’s primary language are typically left to a translated recap after the fact, if one exists at all, or a broadcaster limits its own reach by defaulting to a single language rather than staffing live translators for every market. That’s an accessibility gap as much as a distribution one, since it decides who gets to follow an event as it happens instead of catching up later. There’s no post-production window to translate and dub a recording ahead of release, so the options come down to staffing live translators per language or skipping multi-language coverage.

What we built

We wanted to know how much of that gap we could close without needing a live translation team standing by for every event, so we connected Bitmovin’s Live Encoder directly to Syncwords over SRT, which handles translation, dubbing, and subtitling in real time and hands the results back as additional audio and subtitle streams. It started as a hackathon project, and DVB-subs output is now live in production for the six languages listed above.

Here’s what the integration adds to Bitmovin’s Live Encoder.

  • Multiple audio streams, one per translated language.
  • Multiple subtitle streams, output as WebVtt or IMSC TTML.
  • A direct SRT connection into Syncwords for automated translation and dubbing.
  • Manifests (HLS and DASH) that carry all of it in a single live output.

How the pieces fit together

The core of the setup is an SRT input pointed at Syncwords, configured in CALLER mode against their host and port.

JavaScript
inputs:
  srt:
    my-srt-input:
      properties:
        name: Syncwords SRT input
        mode: CALLER
        host: services.syncwords.com
        port: 30033

One detail worth flagging if you are setting this up yourself. Depending on how your Syncwords account is configured, you may need to switch the SRT mode from LISTENER to PULL on their side for the full integration to work end to end. The template above configures four audio streams and four subtitle streams as an example, and everything downstream is just a matter of repeating the same pattern per language. A complete, end-to-end reference template is below.

Selecting the input audio and subtitle streams

Before an audio or subtitle stream can be encoded, Bitmovin’s Live Encoder needs to know which stream on the SRT input to use. The original hackathon version selected a fixed position on the input, POSITION_ABSOLUTE with a numeric index. That works, but it’s brittle, the position has to match the exact order Syncwords sends streams in, and any change on their side breaks the mapping silently.

We’ve since moved to selectionMode: AUTO paired with a condition on the stream’s LANGUAGE attribute, which selects the correct stream by language instead of position. It’s less error-prone and easier to configure, especially as the number of languages grows.

JavaScript
audio-stream-eng:
  properties:
    inputStreams:
inputId: $/inputs/srt/my-srt-input
        inputPath: /
        selectionMode: AUTO
        position: 0
    codecConfigId: $/configurations/audio/aac/48khz-codec-config
    conditions:
      type: CONDITION
      attribute: LANGUAGE
      operator: ==
      value: eng

The same pattern applies to subtitle streams, shown in the next section.

Configuring multi-audio streams

Adding additional audio languages is straightforward. Each language gets its own DASH adaptation set with a language tag and a label, pointing at its own fmp4 muxing.

JavaScript
audio:
  audio-adaptation-set-eng:
    properties:
      lang: en
      labels:
value: english audio
    representations:
      fmp4:
        eng-audio-representation:
          properties:
            type: TIMELINE
            encodingId: $/encodings/main-encoding
            muxingId: $/encodings/main-encoding/muxings/fmp4/audio-fmp4-muxing-eng
            segmentPath: “dash/audio/eng/

Repeat this block for each additional language, swapping the language code, label, and muxing reference. The main thing to get right is keeping a separate DASH adaptation set per language rather than trying to reuse one.

Configuring DVB-subs

Subtitles come in through the same SRT input as a dedicated subtitle stream. As with audio, we select it by a language condition rather than a fixed position.

JavaScript
inputStreams:
  subtitles:
    dvbSubtitle:
      dvb-sub-input-stream-eng:
        properties:
          inputId: $/inputs/srt/my-srt-input
          inputPath: /
subtilte-stream-eng:
  properties:
    inputStreams:
inputStreamId: $/encodings/main-encoding/inputStreams/subtitles/dvbSubtitle/dvb-sub-input-stream-eng
        selectionMode: AUTO
        position: 0
    codecConfigId: $/configurations/subtitles/webvtt/webvtt-codec-config
    conditions:
      type: CONDITION
      attribute: LANGUAGE
      operator: ==
      value: eng

This needs to be added for each DVB-subs language you want to support.

That input stream then feeds a WebVTT codec configuration and a chunkedText muxing.

JavaScript
subtitles:
  webvtt:
    webvtt-codec-config:
      properties:
        appendOptionalZeroHour: true
chunkedText:
  chunked-text-muxing-eng:
    properties:
      streams:
streamId: $/encodings/main-encoding/streams/subtilte-stream-eng
      segmentLength: 3.84
      segmentNaming: “seg_%number%.vtt”
      outputs:
outputId: “{{outputId}}”
          outputPath: “content/{{name}}/subtitles/vtt/eng”
          acl:
permission: PUBLIC_READ
      streamConditionsMode: DROP_MUXING
      startOffset: 10

The startOffset: 10 matters more than it looks. WebVTT timing gets converted into MPEG-TS timestamps for HLS, and without an offset those timestamps land 10 seconds behind the actual playback position, since that’s the default offset already applied to the video and audio TS muxings. Native HLS players don’t always compensate for that gap on their own, so the subtitle muxing needs the same offset to stay in sync with the rest of the stream.

The complete encoding template

Here’s everything from this post combined into one working template, video, six audio languages, six DVB-subs languages selected by condition, HLS and DASH manifests. Swap in your own SRT credentials and it’s ready to run.

JavaScript
metadata:
  type: LIVE
  name: Live Translation with Syncwords
inputs:
  srt:
    my-srt-input:
      properties:
        name: Syncwords SRT input
        mode: CALLER
        host: services.syncwords.com
        port: 30033
configurations:
  video:
    h264:
      1920-codec-config:
        properties:
          width: 1920
          bitrate: 1000000
          rate: 25
          presetConfiguration: LIVE_LOW_LATENCY
      1024-codec-config:
        properties:
          width: 1024
          bitrate: 500000
          rate: 25
          presetConfiguration: LIVE_LOW_LATENCY
  audio:
    aac:
      48khz-codec-config:
        properties:
          bitrate: 128000
          rate: 48000.0
  subtitles:
    webvtt:
      webvtt-codec-config:
        properties:
          appendOptionalZeroHour: true
encodings:
  main-encoding:
    properties:
      name: “{{name}}”
      cloudRegion: GOOGLE_EUROPE_WEST_1
    inputStreams:
      subtitles:
        dvbSubtitle:
          dvb-sub-input-stream-deu:
            properties:
              inputId: $/inputs/srt/my-srt-input
              inputPath: /
          dvb-sub-input-stream-eng:
            properties:
              inputId: $/inputs/srt/my-srt-input
              inputPath: /
          dvb-sub-input-stream-fra:
            properties:
              inputId: $/inputs/srt/my-srt-input
              inputPath: /
          dvb-sub-input-stream-ita:
            properties:
              inputId: $/inputs/srt/my-srt-input
              inputPath: /
          dvb-sub-input-stream-por:
            properties:
              inputId: $/inputs/srt/my-srt-input
              inputPath: /
          dvb-sub-input-stream-spa:
            properties:
              inputId: $/inputs/srt/my-srt-input
              inputPath: /
    streams:
      1920-h264-stream:
        properties:
          inputStreams:
inputId: $/inputs/srt/my-srt-input
              inputPath: /
              selectionMode: AUTO
          codecConfigId: $/configurations/video/h264/1920-codec-config
      1024-h264-stream:
        properties:
          inputStreams:
inputId: $/inputs/srt/my-srt-input
              inputPath: /
              selectionMode: AUTO
          codecConfigId: $/configurations/video/h264/1024-codec-config
      audio-stream-deu:
        properties:
          inputStreams:
inputId: $/inputs/srt/my-srt-input
              inputPath: /
              selectionMode: AUTO
              position: 0
          codecConfigId: $/configurations/audio/aac/48khz-codec-config
          conditions:
            type: CONDITION
            attribute: LANGUAGE
            operator: ==
            value: deu
      audio-stream-eng:
        properties:
          inputStreams:
inputId: $/inputs/srt/my-srt-input
              inputPath: /
              selectionMode: AUTO
              position: 0
          codecConfigId: $/configurations/audio/aac/48khz-codec-config
          conditions:
            type: CONDITION
            attribute: LANGUAGE
            operator: ==
            value: eng
      audio-stream-fra:
        properties:
          inputStreams:
inputId: $/inputs/srt/my-srt-input
              inputPath: /
              selectionMode: AUTO
              position: 0
          codecConfigId: $/configurations/audio/aac/48khz-codec-config
          conditions:
            type: CONDITION
            attribute: LANGUAGE
            operator: ==
            value: fra
      audio-stream-ita:
        properties:
          inputStreams:
inputId: $/inputs/srt/my-srt-input
              inputPath: /
              selectionMode: AUTO
              position: 0
          codecConfigId: $/configurations/audio/aac/48khz-codec-config
          conditions:
            type: CONDITION
            attribute: LANGUAGE
            operator: ==
            value: ita
      audio-stream-por:
        properties:
          inputStreams:
inputId: $/inputs/srt/my-srt-input
              inputPath: /
              selectionMode: AUTO
              position: 0
          codecConfigId: $/configurations/audio/aac/48khz-codec-config
          conditions:
            type: CONDITION
            attribute: LANGUAGE
            operator: ==
            value: por
      audio-stream-spa:
        properties:
          inputStreams:
inputId: $/inputs/srt/my-srt-input
              inputPath: /
              selectionMode: AUTO
              position: 0
          codecConfigId: $/configurations/audio/aac/48khz-codec-config
          conditions:
            type: CONDITION
            attribute: LANGUAGE
            operator: ==
            value: spa
      subtilte-stream-deu:
        properties:
          inputStreams:
inputStreamId: $/encodings/main-encoding/inputStreams/subtitles/dvbSubtitle/dvb-sub-input-stream-deu
              selectionMode: AUTO
              position: 0
          codecConfigId: $/configurations/subtitles/webvtt/webvtt-codec-config
          conditions:
            type: CONDITION
            attribute: LANGUAGE
            operator: ==
            value: deu
      subtilte-stream-eng:
        properties:
          inputStreams:
inputStreamId: $/encodings/main-encoding/inputStreams/subtitles/dvbSubtitle/dvb-sub-input-stream-eng
              selectionMode: AUTO
              position: 0
          codecConfigId: $/configurations/subtitles/webvtt/webvtt-codec-config
          conditions:
            type: CONDITION
            attribute: LANGUAGE
            operator: ==
            value: eng
      subtilte-stream-fra:
        properties:
          inputStreams:
inputStreamId: $/encodings/main-encoding/inputStreams/subtitles/dvbSubtitle/dvb-sub-input-stream-fra
              selectionMode: AUTO
              position: 0
          codecConfigId: $/configurations/subtitles/webvtt/webvtt-codec-config
          conditions:
            type: CONDITION
            attribute: LANGUAGE
            operator: ==
            value: fra
      subtilte-stream-ita:
        properties:
          inputStreams:
inputStreamId: $/encodings/main-encoding/inputStreams/subtitles/dvbSubtitle/dvb-sub-input-stream-ita
              selectionMode: AUTO
              position: 0
          codecConfigId: $/configurations/subtitles/webvtt/webvtt-codec-config
          conditions:
            type: CONDITION
            attribute: LANGUAGE
            operator: ==
            value: ita
      subtilte-stream-por:
        properties:
          inputStreams:
inputStreamId: $/encodings/main-encoding/inputStreams/subtitles/dvbSubtitle/dvb-sub-input-stream-por
              selectionMode: AUTO
              position: 0
          codecConfigId: $/configurations/subtitles/webvtt/webvtt-codec-config
          conditions:
            type: CONDITION
            attribute: LANGUAGE
            operator: ==
            value: por
      subtilte-stream-spa:
        properties:
          inputStreams:
inputStreamId: $/encodings/main-encoding/inputStreams/subtitles/dvbSubtitle/dvb-sub-input-stream-spa
              selectionMode: AUTO
              position: 0
          codecConfigId: $/configurations/subtitles/webvtt/webvtt-codec-config
          conditions:
            type: CONDITION
            attribute: LANGUAGE
            operator: ==
            value: spa
    muxings:
      fmp4:
        1920-fmp4-muxing-h264:
          properties:
            streams:
streamId: $/encodings/main-encoding/streams/1920-h264-stream
            outputs:
outputId: “{{outputId}}”
                outputPath: “content/{{name}}/dash/video/h264/1920/
                acl:
permission: PUBLIC_READ
            segmentLength: 3.84
            segmentNaming: “seg_%number%.m4s”
        1024-fmp4-muxing-h264:
          properties:
            streams:
streamId: $/encodings/main-encoding/streams/1024-h264-stream
            outputs:
outputId: “{{outputId}}”
                outputPath: “content/{{name}}/dash/video/h264/1024/
                acl:
permission: PUBLIC_READ
            segmentLength: 3.84
            segmentNaming: “seg_%number%.m4s”
        audio-fmp4-muxing-deu:
          properties:
            streams:
streamId: $/encodings/main-encoding/streams/audio-stream-deu
            outputs:
outputId: “{{outputId}}”
                outputPath: “content/{{name}}/dash/audio/deu/
                acl:
permission: PUBLIC_READ
            segmentLength: 3.84
            segmentNaming: “seg_%number%.m4s”
        audio-fmp4-muxing-eng:
          properties:
            streams:
streamId: $/encodings/main-encoding/streams/audio-stream-eng
            outputs:
outputId: “{{outputId}}”
                outputPath: “content/{{name}}/dash/audio/eng/
                acl:
permission: PUBLIC_READ
            segmentLength: 3.84
            segmentNaming: “seg_%number%.m4s”
        audio-fmp4-muxing-fra:
          properties:
            streams:
streamId: $/encodings/main-encoding/streams/audio-stream-fra
            outputs:
outputId: “{{outputId}}”
                outputPath: “content/{{name}}/dash/audio/fra/
                acl:
permission: PUBLIC_READ
            segmentLength: 3.84
            segmentNaming: “seg_%number%.m4s”
        audio-fmp4-muxing-ita:
          properties:
            streams:
streamId: $/encodings/main-encoding/streams/audio-stream-ita
            outputs:
outputId: “{{outputId}}”
                outputPath: “content/{{name}}/dash/audio/ita/
                acl:
permission: PUBLIC_READ
            segmentLength: 3.84
            segmentNaming: “seg_%number%.m4s”
        audio-fmp4-muxing-por:
          properties:
            streams:
streamId: $/encodings/main-encoding/streams/audio-stream-por
            outputs:
outputId: “{{outputId}}”
                outputPath: “content/{{name}}/dash/audio/por/
                acl:
permission: PUBLIC_READ
            segmentLength: 3.84
            segmentNaming: “seg_%number%.m4s”
        audio-fmp4-muxing-spa:
          properties:
            streams:
streamId: $/encodings/main-encoding/streams/audio-stream-spa
            outputs:
outputId: “{{outputId}}”
                outputPath: “content/{{name}}/dash/audio/spa/
                acl:
permission: PUBLIC_READ
            segmentLength: 3.84
            segmentNaming: “seg_%number%.m4s”
      chunkedText:
        chunked-text-muxing-deu:
          properties:
            streams:
streamId: $/encodings/main-encoding/streams/subtilte-stream-deu
            segmentLength: 3.84
            segmentNaming: “seg_%number%.vtt”
            outputs:
outputId: “{{outputId}}”
                outputPath: “content/{{name}}/subtitles/vtt/deu”
                acl:
permission: PUBLIC_READ
            streamConditionsMode: DROP_MUXING
            startOffset: 10
        chunked-text-muxing-eng:
          properties:
            streams:
streamId: $/encodings/main-encoding/streams/subtilte-stream-eng
            segmentLength: 3.84
            segmentNaming: “seg_%number%.vtt”
            outputs:
outputId: “{{outputId}}”
                outputPath: “content/{{name}}/subtitles/vtt/eng”
                acl:
permission: PUBLIC_READ
            streamConditionsMode: DROP_MUXING
            startOffset: 10
        chunked-text-muxing-fra:
          properties:
            streams:
streamId: $/encodings/main-encoding/streams/subtilte-stream-fra
            segmentLength: 3.84
            segmentNaming: “seg_%number%.vtt”
            outputs:
outputId: “{{outputId}}”
                outputPath: “content/{{name}}/subtitles/vtt/fra”
                acl:
permission: PUBLIC_READ
            streamConditionsMode: DROP_MUXING
            startOffset: 10
        chunked-text-muxing-ita:
          properties:
            streams:
streamId: $/encodings/main-encoding/streams/subtilte-stream-ita
            segmentLength: 3.84
            segmentNaming: “seg_%number%.vtt”
            outputs:
outputId: “{{outputId}}”
                outputPath: “content/{{name}}/subtitles/vtt/ita”
                acl:
permission: PUBLIC_READ
            streamConditionsMode: DROP_MUXING
            startOffset: 10
        chunked-text-muxing-por:
          properties:
            streams:
streamId: $/encodings/main-encoding/streams/subtilte-stream-por
            segmentLength: 3.84
            segmentNaming: “seg_%number%.vtt”
            outputs:
outputId: “{{outputId}}”
                outputPath: “content/{{name}}/subtitles/vtt/por”
                acl:
permission: PUBLIC_READ
            streamConditionsMode: DROP_MUXING
            startOffset: 10
        chunked-text-muxing-spa:
          properties:
            streams:
streamId: $/encodings/main-encoding/streams/subtilte-stream-spa
            segmentLength: 3.84
            segmentNaming: “seg_%number%.vtt”
            outputs:
outputId: “{{outputId}}”
                outputPath: “content/{{name}}/subtitles/vtt/spa”
                acl:
permission: PUBLIC_READ
            streamConditionsMode: DROP_MUXING
            startOffset: 10
    live:
      start:
        properties:
          streamKey: my-stream-key
          manifestGenerator: V2
          autoShutdownConfiguration:
            streamTimeoutMinutes: 180
            bytesReadTimeoutSeconds: 300
          hlsManifests:
manifestId: $/manifests/hls/defaultapi/main-hls-manifest
              timeshift: 120
              liveEdgeOffset: 3.84
              insertProgramDateTime: true
          dashManifests:
manifestId: $/manifests/dash/timeline-dash-manifest
              timeshift: 120
              liveEdgeOffset: 3.84
manifests:
  hls:
    defaultapi:
      main-hls-manifest:
        properties:
          name: Main HLS Manifest
          manifestName: main.m3u8
          encodingId: $/encodings/main-encoding
          outputs:
outputId: “{{outputId}}”
              outputPath: “content/{{name}}/
              acl:
permission: PUBLIC_READ
  dash:
    timeline-dash-manifest:
      properties:
        name: Timeline Dash Manifest
        manifestName: stream.mpd
        outputs:
outputId: “{{outputId}}”
            outputPath: “content/{{name}}/
            acl:
permission: PUBLIC_READ
        periods:
          period-1:
            properties:
              start: 0
            adaptationsets:
              video:
                video-adaptation-set:
                  properties:
                    labels:
value: video adaptation set
                  representations:
                    fmp4:
                      1920-representation:
                        properties:
                          type: TIMELINE
                          encodingId: $/encodings/main-encoding
                          muxingId: $/encodings/main-encoding/muxings/fmp4/1920-fmp4-muxing-h264
                          segmentPath: “dash/video/h264/1920/
                      1024-representation:
                        properties:
                          type: TIMELINE
                          encodingId: $/encodings/main-encoding
                          muxingId: $/encodings/main-encoding/muxings/fmp4/1024-fmp4-muxing-h264
                          segmentPath: “dash/video/h264/1024/
              audio:
                audio-adaptation-set-deu:
                  properties:
                    lang: deu
                    labels:
value: german audio
                  representations:
                    fmp4:
                      deu-audio-representation:
                        properties:
                          type: TIMELINE
                          encodingId: $/encodings/main-encoding
                          muxingId: $/encodings/main-encoding/muxings/fmp4/audio-fmp4-muxing-deu
                          segmentPath: “dash/audio/deu/
                audio-adaptation-set-eng:
                  properties:
                    lang: en
                    labels:
value: english audio
                  representations:
                    fmp4:
                      eng-audio-representation:
                        properties:
                          type: TIMELINE
                          encodingId: $/encodings/main-encoding
                          muxingId: $/encodings/main-encoding/muxings/fmp4/audio-fmp4-muxing-eng
                          segmentPath: “dash/audio/eng/
                audio-adaptation-set-fra:
                  properties:
                    lang: fra
                    labels:
value: french audio
                  representations:
                    fmp4:
                      fra-audio-representation:
                        properties:
                          type: TIMELINE
                          encodingId: $/encodings/main-encoding
                          muxingId: $/encodings/main-encoding/muxings/fmp4/audio-fmp4-muxing-fra
                          segmentPath: “dash/audio/fra/
                audio-adaptation-set-ita:
                  properties:
                    lang: ita
                    labels:
value: italian audio
                  representations:
                    fmp4:
                      ita-audio-representation:
                        properties:
                          type: TIMELINE
                          encodingId: $/encodings/main-encoding
                          muxingId: $/encodings/main-encoding/muxings/fmp4/audio-fmp4-muxing-ita
                          segmentPath: “dash/audio/ita/
                audio-adaptation-set-por:
                  properties:
                    lang: por
                    labels:
value: portuguese audio
                  representations:
                    fmp4:
                      por-audio-representation:
                        properties:
                          type: TIMELINE
                          encodingId: $/encodings/main-encoding
                          muxingId: $/encodings/main-encoding/muxings/fmp4/audio-fmp4-muxing-por
                          segmentPath: “dash/audio/por/
                audio-adaptation-set-spa:
                  properties:
                    lang: spa
                    labels:
value: spanish audio
                  representations:
                    fmp4:
                      spa-audio-representation:
                        properties:
                          type: TIMELINE
                          encodingId: $/encodings/main-encoding
                          muxingId: $/encodings/main-encoding/muxings/fmp4/audio-fmp4-muxing-spa
                          segmentPath: “dash/audio/spa/

Setting up Syncwords

The Syncwords setup wizard is genuinely easy to work through. A few things we picked up along the way.

  • Cloned Voice. For translated audio, this option does a good job of imitating the original speaker’s voice rather than dropping in a generic voiceover.
  • SRT pull clients. Their official documentation says only one pull client is supported, but their team confirmed on a call that it actually handles up to 10, useful to know if you are fanning out to multiple encoders.
  • Output formats. Bitmovin currently supports DVB-subs. DVB-teletext decoding support is still in development.
  • DVB-subs. Picture based rather than text based, so any character set is supported at the source. We OCR them back into text with Tesseract to produce WebVTT, and in production today we only have language models installed for German, English, French, Italian, Portuguese, and Spanish. That’s the current limit on which DVB-subs languages are supported end to end. OCR accuracy also varies with source image quality and font, so it’s worth spot-checking output for a given feed rather than assuming perfect transcription.

Results

Here’s what the output looks like in practice. A default HLS manifest with four audio languages and four subtitle tracks attached.

#EXTM3U

#EXT-X-INDEPENDENT-SEGMENTS

#EXT-X-VERSION:6

#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID=”AUDIO”,NAME=”audio_0.m3u8″,LANGUAGE=”en”,URI=”audio_0.m3u8″

#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID=”AUDIO”,NAME=”audio_1.m3u8″,LANGUAGE=”de”,URI=”audio_1.m3u8″

#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID=”AUDIO”,NAME=”audio_2.m3u8″,LANGUAGE=”fr”,URI=”audio_2.m3u8″

#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID=”AUDIO”,NAME=”audio_3.m3u8″,LANGUAGE=”es”,URI=”audio_3.m3u8″

#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID=”SUBTITLES”,NAME=”subtitle_0.m3u8″,LANGUAGE=”en”,URI=”subtitle_0.m3u8″

#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID=”SUBTITLES”,NAME=”subtitle_1.m3u8″,LANGUAGE=”de”,URI=”subtitle_1.m3u8″

#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID=”SUBTITLES”,NAME=”subtitle_2.m3u8″,LANGUAGE=”fr”,URI=”subtitle_2.m3u8″

#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID=”SUBTITLES”,NAME=”subtitle_3.m3u8″,LANGUAGE=”es”,URI=”subtitle_3.m3u8″

#EXT-X-STREAM-INF:BANDWIDTH=628000,CODECS=”avc1.64001F,mp4a.40.2″,RESOLUTION=1024×576,AUDIO=”AUDIO”,SUBTITLES=”SUBTITLES”,FRAME-RATE=25.0

video_0.m3u8

#EXT-X-STREAM-INF:BANDWIDTH=1128000,CODECS=”avc1.640028,mp4a.40.2″,RESOLUTION=1920×1080,AUDIO=”AUDIO”,SUBTITLES=”SUBTITLES”,FRAME-RATE=25.0

video_1.m3u8

You can see a live player demo and the underlying HLS and DASH endpoints from our proof of concept run, which we’re happy to share with anyone who wants to see it working end to end. If you want to try switching between the multiple audio and subtitle tracks yourself, our test stream demo lets you do exactly that.

What’s next

A few things are still on our list. We want to add support for side-loading additional Tesseract language models, so DVB-subs support can grow past the current six languages without waiting on a full release. We’re also looking at automatic audio and subtitle stream selection and passthrough, extending PerTitle Streams to cover audio and subtitle tracks, and adding DVB-TTML as an additional subtitle output format.

Want to try multi-language live streaming yourself? Sign up for a trial of Bitmovin’s Live Encoder and start testing.

Armin Trattnig

Tech Lead Live Encoding

Armin Trattnig has a long history of working on the Encoding products of BItmovin. Armin is part of the Bitmovin team since 2012 and graduated from the Alpen-Adria University Klagenfurt, specializing in Distributed Multimedia Systems. He currently has the technical ownership of the Live Encoding product.


Related Posts

Developers

Hackathon spotlight: Upgrading MoQ support in Bitmovin’s Player Web X

Join our newsletter and stay informed.

Get to hear first when we publish something new.