From e86bfd6dbe71fc809b6e2c7d6ec0da3ddacef104 Mon Sep 17 00:00:00 2001 From: olly Date: Tue, 6 Dec 2016 02:28:57 -0800 Subject: [PATCH] Improvements for 608 positioning - Infer likely left/center/right alignment for pop-on captions. This makes the rendering much better in practice, particularly when the captions were intended to be center aligned. - Fix line anchoring. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=141156222 --- .../google/android/exoplayer2/text/Cue.java | 20 ++++- .../exoplayer2/text/cea/Cea608Decoder.java | 87 +++++++++++++------ .../text/webvtt/WebvttCueParser.java | 8 +- .../exoplayer2/ui/SubtitlePainter.java | 2 +- 4 files changed, 84 insertions(+), 33 deletions(-) diff --git a/library/src/main/java/com/google/android/exoplayer2/text/Cue.java b/library/src/main/java/com/google/android/exoplayer2/text/Cue.java index 93b1dc1d9a..1c29f10c84 100644 --- a/library/src/main/java/com/google/android/exoplayer2/text/Cue.java +++ b/library/src/main/java/com/google/android/exoplayer2/text/Cue.java @@ -95,10 +95,22 @@ public class Cue { *

* {@link #LINE_TYPE_NUMBER} indicates that {@link #line} is a line number, where the size of each * line is taken to be the size of the first line of the cue. When {@link #line} is greater than - * or equal to 0, lines count from the start of the viewport (the first line is numbered 0). When - * {@link #line} is negative, lines count from the end of the viewport (the last line is numbered - * -1). For horizontal text the size of the first line of the cue is its height, and the start - * and end of the viewport are the top and bottom respectively. + * or equal to 0 lines count from the start of the viewport, with 0 indicating zero offset from + * the start edge. When {@link #line} is negative lines count from the end of the viewport, with + * -1 indicating zero offset from the end edge. For horizontal text the line spacing is the height + * of the first line of the cue, and the start and end of the viewport are the top and bottom + * respectively. + *

+ * Note that it's particularly important to consider the effect of {@link #lineAnchor} when using + * {@link #LINE_TYPE_NUMBER}. {@code (line == 0 && lineAnchor == ANCHOR_TYPE_START)} positions a + * (potentially multi-line) cue at the very top of the viewport. + * {@code (line == -1 && lineAnchor == ANCHOR_TYPE_END)} positions a (potentially multi-line) cue + * at the very bottom of the viewport. {@code (line == 0 && lineAnchor == ANCHOR_TYPE_END)} + * and {@code (line == -1 && lineAnchor == ANCHOR_TYPE_START)} position cues entirely outside of + * the viewport. {@code (line == 1 && lineAnchor == ANCHOR_TYPE_END)} positions a cue so that only + * the last line is visible at the top of the viewport. + * {@code (line == -2 && lineAnchor == ANCHOR_TYPE_START)} position a cue so that only its first + * line is visible at the bottom of the viewport. */ @LineType public final int lineType; diff --git a/library/src/main/java/com/google/android/exoplayer2/text/cea/Cea608Decoder.java b/library/src/main/java/com/google/android/exoplayer2/text/cea/Cea608Decoder.java index dfa0dcf8f5..3ae8ded9ba 100644 --- a/library/src/main/java/com/google/android/exoplayer2/text/cea/Cea608Decoder.java +++ b/library/src/main/java/com/google/android/exoplayer2/text/cea/Cea608Decoder.java @@ -404,14 +404,18 @@ public final class Cea608Decoder extends CeaDecoder { // cc2 - 0|1|N|ATTRBTE|U // N is the next row down toggle, ATTRBTE is the 4-byte encoded attribute, and U is the - // underline toggle. The next row down toggle isn't applicable for roll-up captions. - boolean nextRowDown = captionMode != CC_MODE_ROLL_UP && (cc2 & 0x20) != 0; - if (row != currentCueBuilder.getRow() || nextRowDown) { + // underline toggle. + boolean nextRowDown = (cc2 & 0x20) != 0; + if (nextRowDown) { + row++; + } + + if (row != currentCueBuilder.getRow()) { if (captionMode != CC_MODE_ROLL_UP && !currentCueBuilder.isEmpty()) { currentCueBuilder = new CueBuilder(captionMode, captionRowCount); cueBuilders.add(currentCueBuilder); } - currentCueBuilder.setRow(nextRowDown ? ++row : row); + currentCueBuilder.setRow(row); } if ((cc2 & 0x01) == 0x01) { @@ -492,7 +496,10 @@ public final class Cea608Decoder extends CeaDecoder { private List getDisplayCues() { List displayCues = new ArrayList<>(); for (int i = 0; i < cueBuilders.size(); i++) { - displayCues.add(cueBuilders.get(i).build()); + Cue cue = cueBuilders.get(i).build(); + if (cue != null) { + displayCues.add(cue); + } } return displayCues; } @@ -727,36 +734,62 @@ public final class Cea608Decoder extends CeaDecoder { public Cue build() { SpannableStringBuilder cueString = new SpannableStringBuilder(); - - // add any rolled up captions, separated by new lines + // Add any rolled up captions, separated by new lines. for (int i = 0; i < rolledUpCaptions.size(); i++) { cueString.append(rolledUpCaptions.get(i)); cueString.append('\n'); } - - // add the current line + // Add the current line. cueString.append(buildSpannableString()); - float position = (float) (indent + tabOffset) / SCREEN_CHARWIDTH; - // adjust the position to fit within the safe area - position = position * 0.8f + 0.1f; - - float line; - int lineType; - if (captionMode == CC_MODE_ROLL_UP) { - lineType = Cue.LINE_TYPE_NUMBER; - line = row - BASE_ROW; - // adjust the line to fit within the safe area - line--; - } else { - lineType = Cue.LINE_TYPE_FRACTION; - line = (float) row / BASE_ROW; - // adjust the line to fit within the safe area - line = line * 0.8f + 0.1f; + if (cueString.length() == 0) { + // The cue is empty. + return null; } - return new Cue(cueString, Alignment.ALIGN_NORMAL, line, lineType, Cue.ANCHOR_TYPE_END, - position, Cue.ANCHOR_TYPE_START, Cue.DIMEN_UNSET); + float position; + int positionAnchor; + // The number of empty columns before the start of the text, in the range [0-31]. + int startPadding = indent + tabOffset; + // The number of empty columns after the end of the text, in the same range. + int endPadding = SCREEN_CHARWIDTH - startPadding - cueString.length(); + int startEndPaddingDelta = startPadding - endPadding; + if (captionMode == CC_MODE_POP_ON && Math.abs(startEndPaddingDelta) < 3) { + // Treat approximately centered pop-on captions are middle aligned. + position = 0.5f; + positionAnchor = Cue.ANCHOR_TYPE_MIDDLE; + } else if (captionMode == CC_MODE_POP_ON && startEndPaddingDelta > 0) { + // Treat pop-on captions with less padding at the end than the start as end aligned. + position = (float) (SCREEN_CHARWIDTH - endPadding) / SCREEN_CHARWIDTH; + // Adjust the position to fit within the safe area. + position = position * 0.8f + 0.1f; + positionAnchor = Cue.ANCHOR_TYPE_END; + } else { + // For all other cases assume start aligned. + position = (float) startPadding / SCREEN_CHARWIDTH; + // Adjust the position to fit within the safe area. + position = position * 0.8f + 0.1f; + positionAnchor = Cue.ANCHOR_TYPE_START; + } + + int lineAnchor; + int line; + // Note: Row indices are in the range [1-15]. + if (captionMode == CC_MODE_ROLL_UP || row > (BASE_ROW / 2)) { + lineAnchor = Cue.ANCHOR_TYPE_END; + line = row - BASE_ROW; + // Two line adjustments. The first is because line indices from the bottom of the window + // start from -1 rather than 0. The second is a blank row to act as the safe area. + line -= 2; + } else { + lineAnchor = Cue.ANCHOR_TYPE_START; + // Line indices from the top of the window start from 0, but we want a blank row to act as + // the safe area. As a result no adjustment is necessary. + line = row; + } + + return new Cue(cueString, Alignment.ALIGN_NORMAL, line, Cue.LINE_TYPE_NUMBER, lineAnchor, + position, positionAnchor, Cue.DIMEN_UNSET); } @Override diff --git a/library/src/main/java/com/google/android/exoplayer2/text/webvtt/WebvttCueParser.java b/library/src/main/java/com/google/android/exoplayer2/text/webvtt/WebvttCueParser.java index c63004e1cd..932d4a6bed 100644 --- a/library/src/main/java/com/google/android/exoplayer2/text/webvtt/WebvttCueParser.java +++ b/library/src/main/java/com/google/android/exoplayer2/text/webvtt/WebvttCueParser.java @@ -256,7 +256,13 @@ import java.util.regex.Pattern; if (s.endsWith("%")) { builder.setLine(WebvttParserUtil.parsePercentage(s)).setLineType(Cue.LINE_TYPE_FRACTION); } else { - builder.setLine(Integer.parseInt(s)).setLineType(Cue.LINE_TYPE_NUMBER); + int lineNumber = Integer.parseInt(s); + if (lineNumber < 0) { + // WebVTT defines line -1 as last visible row when lineAnchor is ANCHOR_TYPE_START, where-as + // Cue defines it to be the first row that's not visible. + lineNumber--; + } + builder.setLine(lineNumber).setLineType(Cue.LINE_TYPE_NUMBER); } } diff --git a/library/src/main/java/com/google/android/exoplayer2/ui/SubtitlePainter.java b/library/src/main/java/com/google/android/exoplayer2/ui/SubtitlePainter.java index 8c3ac77cb2..de461ecf0d 100644 --- a/library/src/main/java/com/google/android/exoplayer2/ui/SubtitlePainter.java +++ b/library/src/main/java/com/google/android/exoplayer2/ui/SubtitlePainter.java @@ -252,7 +252,7 @@ import com.google.android.exoplayer2.util.Util; if (cueLine >= 0) { anchorPosition = Math.round(cueLine * firstLineHeight) + parentTop; } else { - anchorPosition = Math.round(cueLine * firstLineHeight) + parentBottom; + anchorPosition = Math.round((cueLine + 1) * firstLineHeight) + parentBottom; } } textTop = cueLineAnchor == Cue.ANCHOR_TYPE_END ? anchorPosition - textHeight