Skip to content

Commit

Permalink
Support Audio Passthrough
Browse files Browse the repository at this point in the history
Allow for Audio passthrough, by including the MediaCodecAudioRenderer
first in the list of candidate Audio Renderers. Additionally, allow
users to disable passthrough support.

Fixes #107 (maybe, I reproduced a few times, but no longer can..)
Fixes #38
  • Loading branch information
kiall committed Mar 14, 2017
1 parent 7e5b62f commit 5e9e468
Show file tree
Hide file tree
Showing 5 changed files with 84 additions and 12 deletions.
3 changes: 3 additions & 0 deletions app/src/main/java/ie/macinnes/tvheadend/Constants.java
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ public class Constants {
public static final String KEY_SETUP_COMPLETE = "SETUP-COMPLETE";
public static final String KEY_HTSP_STREAM_PROFILE = "htsp_stream_profile";

// Audio Preferences Keys and Values
public static final String KEY_AUDIO_PASSTHROUGH_DECODER_ENABLED = "audio_passthrough_decodeder_enabled";

// Advanced Preferences Keys and Values
public static final String KEY_FFMPEG_AUDIO_ENABLED = "ffmpeg_audio_enabled";
public static final String KEY_SHIELD_WORKAROUND_ENABLED = "shield_workaround_enabled";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@

import android.accounts.Account;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.support.v7.preference.PreferenceManager;
import android.util.Log;
Expand All @@ -26,7 +25,6 @@
import ie.macinnes.tvheadend.MiscUtils;
import ie.macinnes.tvheadend.R;
import ie.macinnes.tvheadend.account.AccountUtils;
import ie.macinnes.tvheadend.tvinput.TvInputService;


public class MigrateUtils {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,9 @@
import com.google.android.exoplayer2.drm.DrmSessionManager;
import com.google.android.exoplayer2.drm.FrameworkMediaCrypto;
import com.google.android.exoplayer2.ext.ffmpeg.FfmpegAudioRenderer;
import com.google.android.exoplayer2.mediacodec.MediaCodecInfo;
import com.google.android.exoplayer2.mediacodec.MediaCodecSelector;
import com.google.android.exoplayer2.mediacodec.MediaCodecUtil;
import com.google.android.exoplayer2.trackselection.TrackSelector;
import com.google.android.exoplayer2.video.MediaCodecVideoRenderer;
import com.google.android.exoplayer2.video.VideoRendererEventListener;
Expand All @@ -56,17 +58,42 @@ protected void buildAudioRenderers(Context context, Handler mainHandler, DrmSess
AudioCapabilities audioCapabilities = AudioCapabilities.getCapabilities(context);

SharedPreferences sharedPreferences = context.getSharedPreferences(Constants.PREFERENCE_TVHEADEND, Context.MODE_PRIVATE);
final boolean enablePassthroughDecoder = sharedPreferences.getBoolean(Constants.KEY_AUDIO_PASSTHROUGH_DECODER_ENABLED, true);

// Native Audio Decoders
Log.d(TAG, "Adding MediaCodecAudioRenderer");
MediaCodecSelector mediaCodecSelector = buildMediaCodecSelector(enablePassthroughDecoder);
out.add(new MediaCodecAudioRenderer(mediaCodecSelector, drmSessionManager,
true, mainHandler, eventListener, audioCapabilities));

// FFMpeg Audio Decoder
if (sharedPreferences.getBoolean(Constants.KEY_FFMPEG_AUDIO_ENABLED, true)) {
// FFMpeg Audio Decoder
Log.d(TAG, "Adding FfmpegAudioRenderer");
out.add(new FfmpegAudioRenderer(mainHandler, eventListener, audioCapabilities));
}
}

// Native Audio Decoders
Log.d(TAG, "Adding MediaCodecAudioRenderer");
out.add(new MediaCodecAudioRenderer(MediaCodecSelector.DEFAULT, drmSessionManager,
true, mainHandler, eventListener, audioCapabilities));
/**
* Builds a MediaCodecSelector that can explicitly disable audio passthrough
*
* @param enablePassthroughDecoder
* @return
*/
private MediaCodecSelector buildMediaCodecSelector(final boolean enablePassthroughDecoder) {
return new MediaCodecSelector() {
@Override
public MediaCodecInfo getDecoderInfo(String mimeType, boolean requiresSecureDecoder) throws MediaCodecUtil.DecoderQueryException {
return MediaCodecUtil.getDecoderInfo(mimeType, requiresSecureDecoder);
}

@Override
public MediaCodecInfo getPassthroughDecoderInfo() throws MediaCodecUtil.DecoderQueryException {
if (enablePassthroughDecoder) {
return MediaCodecUtil.getPassthroughDecoderInfo();
}
return null;
}
};
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
import com.google.android.exoplayer2.trackselection.FixedTrackSelection;
import com.google.android.exoplayer2.trackselection.TrackSelection;
import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.Util;


public class TvheadendTrackSelector extends DefaultTrackSelector {
Expand Down Expand Up @@ -65,6 +67,40 @@ public boolean onSelectTrack(int type, String trackId) {
return true;
}

@Override
protected TrackSelection[] selectTracks(RendererCapabilities[] rendererCapabilities, TrackGroupArray[] rendererTrackGroupArrays, int[][][] rendererFormatSupports) throws ExoPlaybackException {
// Apparently, it's very easy to end up choosing multiple audio track renderers, e.g ffmpeg
// decode and MediaCodec passthrough. When this happens we end up with a Multiple renderer
// media clocks enabled IllegalStateException exception (see Issue #107).
TrackSelection[] trackSelections = super.selectTracks(rendererCapabilities, rendererTrackGroupArrays, rendererFormatSupports);

// If we made multiple audio track selections, keep only one of them.
// TODO: Make this smarter, if the user prefers English, and we select a passthrough German
// track and a English mpeg-2 audio track, we really should keep the english one over the
// German one.
int selectedAudioRendererIndex = -1;
for (int trackSelectionIndex = 0; trackSelectionIndex < trackSelections.length; trackSelectionIndex++) {
final TrackSelection trackSelection = trackSelections[trackSelectionIndex];

if (trackSelection == null || !MimeTypes.isAudio(trackSelection.getSelectedFormat().sampleMimeType)) {
// Skip if, a) renderer has no candidate, b) not an audio renderer
continue;
}

if (selectedAudioRendererIndex != -1) {
// If we already made a selection, discard this extra selection
trackSelections[trackSelectionIndex] = null;
Log.d(TAG, "Discarding Audio Track Selection");
continue;
}

// Otherwise, flag that we've made our selection
selectedAudioRendererIndex = trackSelectionIndex;
}

return trackSelections;
}

protected TrackSelection selectVideoTrack(
RendererCapabilities rendererCapabilities, TrackGroupArray groups, int[][] formatSupport,
int maxVideoWidth, int maxVideoHeight, boolean allowNonSeamlessAdaptiveness,
Expand Down
18 changes: 13 additions & 5 deletions app/src/main/res/xml/preferences.xml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,19 @@
xmlns:android="http://schemas.android.com/apk/res/android"
android:title="TVHeadend Settings">

<PreferenceCategory android:title="Audio and Video">
<CheckBoxPreference
android:key="audio_passthrough_decodeder_enabled"
android:title="Enable Audio Passthrough"
android:defaultValue="true"
android:summary="You must also ensure the system audio settings allows passthrough"/>

<CheckBoxPreference
android:key="ffmpeg_audio_enabled"
android:title="Enable FFMpeg Audio Codecs"
android:defaultValue="true"/>
</PreferenceCategory>

<PreferenceCategory android:title="EPG">
<CheckBoxPreference
android:key="epg_sync_enabled"
Expand All @@ -45,11 +58,6 @@
</PreferenceCategory>

<PreferenceCategory android:title="Advanced Settings">
<CheckBoxPreference
android:key="ffmpeg_audio_enabled"
android:title="Enable FFMpeg Audio Renderer"
android:defaultValue="true" />

<CheckBoxPreference
android:key="shield_workaround_enabled"
android:title="Enable nVidia Shield Workaround"
Expand Down

0 comments on commit 5e9e468

Please sign in to comment.