Skip to content
Toggle navigation
P
Projects
G
Groups
S
Snippets
Help
SDK
/
exoplayer
This project
Loading...
Sign in
Toggle navigation
Go to a project
Project
Repository
Issues
0
Merge Requests
0
Pipelines
Wiki
Snippets
Settings
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Commit
32bed828
authored
Dec 01, 2022
by
samrobinson
Committed by
Ian Baker
Dec 12, 2022
Browse files
Options
_('Browse Files')
Download
Email Patches
Plain Diff
Add an option to force a silent audio track.
PiperOrigin-RevId: 492154544
parent
90d4fdcf
Hide whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
744 additions
and
5 deletions
library/transformer/src/main/java/com/google/android/exoplayer2/transformer/AudioTranscodingSamplePipeline.java
library/transformer/src/main/java/com/google/android/exoplayer2/transformer/SilentAudioGenerator.java
library/transformer/src/main/java/com/google/android/exoplayer2/transformer/Transformer.java
library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformerInternal.java
library/transformer/src/test/java/com/google/android/exoplayer2/transformer/SilentAudioGeneratorTest.java
library/transformer/src/test/java/com/google/android/exoplayer2/transformer/TransformerEndToEndTest.java
testdata/src/test/assets/transformerdumps/mp4/sample.mp4.silentaudio.dump
library/transformer/src/main/java/com/google/android/exoplayer2/transformer/AudioTranscodingSamplePipeline.java
View file @
32bed828
...
@@ -41,6 +41,7 @@ import org.checkerframework.dataflow.qual.Pure;
...
@@ -41,6 +41,7 @@ import org.checkerframework.dataflow.qual.Pure;
private
static
final
int
DEFAULT_ENCODER_BITRATE
=
128
*
1024
;
private
static
final
int
DEFAULT_ENCODER_BITRATE
=
128
*
1024
;
@Nullable
private
final
SilentAudioGenerator
silentAudioGenerator
;
private
final
DecoderInputBuffer
inputBuffer
;
private
final
DecoderInputBuffer
inputBuffer
;
private
final
AudioProcessingPipeline
audioProcessingPipeline
;
private
final
AudioProcessingPipeline
audioProcessingPipeline
;
private
final
Codec
encoder
;
private
final
Codec
encoder
;
...
@@ -52,12 +53,14 @@ import org.checkerframework.dataflow.qual.Pure;
...
@@ -52,12 +53,14 @@ import org.checkerframework.dataflow.qual.Pure;
private
long
nextEncoderInputBufferTimeUs
;
private
long
nextEncoderInputBufferTimeUs
;
private
long
encoderBufferDurationRemainder
;
private
long
encoderBufferDurationRemainder
;
// TODO(b/260618558): Move silent audio generation upstream of this component.
public
AudioTranscodingSamplePipeline
(
public
AudioTranscodingSamplePipeline
(
Format
inputFormat
,
Format
inputFormat
,
long
streamStartPositionUs
,
long
streamStartPositionUs
,
long
streamOffsetUs
,
long
streamOffsetUs
,
TransformationRequest
transformationRequest
,
TransformationRequest
transformationRequest
,
ImmutableList
<
AudioProcessor
>
audioProcessors
,
ImmutableList
<
AudioProcessor
>
audioProcessors
,
long
forceSilentAudioDurationUs
,
Codec
.
EncoderFactory
encoderFactory
,
Codec
.
EncoderFactory
encoderFactory
,
MuxerWrapper
muxerWrapper
,
MuxerWrapper
muxerWrapper
,
Listener
listener
,
Listener
listener
,
...
@@ -71,6 +74,16 @@ import org.checkerframework.dataflow.qual.Pure;
...
@@ -71,6 +74,16 @@ import org.checkerframework.dataflow.qual.Pure;
muxerWrapper
,
muxerWrapper
,
listener
);
listener
);
if
(
forceSilentAudioDurationUs
!=
C
.
TIME_UNSET
)
{
silentAudioGenerator
=
new
SilentAudioGenerator
(
forceSilentAudioDurationUs
,
inputFormat
.
sampleRate
,
Util
.
getPcmFrameSize
(
C
.
ENCODING_PCM_16BIT
,
inputFormat
.
channelCount
));
}
else
{
silentAudioGenerator
=
null
;
}
inputBuffer
=
new
DecoderInputBuffer
(
BUFFER_REPLACEMENT_MODE_DISABLED
);
inputBuffer
=
new
DecoderInputBuffer
(
BUFFER_REPLACEMENT_MODE_DISABLED
);
encoderInputBuffer
=
new
DecoderInputBuffer
(
BUFFER_REPLACEMENT_MODE_DISABLED
);
encoderInputBuffer
=
new
DecoderInputBuffer
(
BUFFER_REPLACEMENT_MODE_DISABLED
);
encoderOutputBuffer
=
new
DecoderInputBuffer
(
BUFFER_REPLACEMENT_MODE_DISABLED
);
encoderOutputBuffer
=
new
DecoderInputBuffer
(
BUFFER_REPLACEMENT_MODE_DISABLED
);
...
@@ -160,11 +173,17 @@ import org.checkerframework.dataflow.qual.Pure;
...
@@ -160,11 +173,17 @@ import org.checkerframework.dataflow.qual.Pure;
@Override
@Override
protected
boolean
processDataUpToMuxer
()
throws
TransformationException
{
protected
boolean
processDataUpToMuxer
()
throws
TransformationException
{
if
(
audioProcessingPipeline
.
isOperational
())
{
if
(!
audioProcessingPipeline
.
isOperational
())
{
return
feedEncoderFromProcessingPipeline
()
||
feedProcessingPipelineFromInput
();
return
silentAudioGenerator
==
null
?
feedEncoderFromInput
()
:
feedEncoderFromSilence
();
}
else
{
return
feedEncoderFromInput
();
}
}
if
(
feedEncoderFromProcessingPipeline
())
{
return
true
;
}
return
silentAudioGenerator
==
null
?
feedProcessingPipelineFromInput
()
:
feedProcessingPipelineFromSilence
();
}
}
@Override
@Override
...
@@ -269,6 +288,45 @@ import org.checkerframework.dataflow.qual.Pure;
...
@@ -269,6 +288,45 @@ import org.checkerframework.dataflow.qual.Pure;
}
}
/**
/**
* Attempts to pass silent audio to the encoder.
*
* @return Whether it may be possible to feed more data immediately by calling this method again.
*/
private
boolean
feedEncoderFromSilence
()
throws
TransformationException
{
checkNotNull
(
silentAudioGenerator
);
if
(!
encoder
.
maybeDequeueInputBuffer
(
encoderInputBuffer
))
{
return
false
;
}
if
(
silentAudioGenerator
.
isEnded
())
{
queueEndOfStreamToEncoder
();
return
false
;
}
ByteBuffer
silence
=
silentAudioGenerator
.
getBuffer
();
feedEncoder
(
silence
);
return
true
;
}
/**
* Attempts to feed silent audio to the {@link AudioProcessingPipeline}.
*
* @return Whether it may be possible to feed more data immediately by calling this method again.
*/
private
boolean
feedProcessingPipelineFromSilence
()
{
checkNotNull
(
silentAudioGenerator
);
if
(
silentAudioGenerator
.
isEnded
())
{
audioProcessingPipeline
.
queueEndOfStream
();
return
false
;
}
checkState
(!
audioProcessingPipeline
.
isEnded
());
ByteBuffer
silence
=
silentAudioGenerator
.
getBuffer
();
audioProcessingPipeline
.
queueInput
(
silence
);
return
!
silence
.
hasRemaining
();
}
/**
* Feeds as much data as possible between the current position and limit of the specified {@link
* Feeds as much data as possible between the current position and limit of the specified {@link
* ByteBuffer} to the encoder, and advances its position by the number of bytes fed.
* ByteBuffer} to the encoder, and advances its position by the number of bytes fed.
*/
*/
...
...
library/transformer/src/main/java/com/google/android/exoplayer2/transformer/SilentAudioGenerator.java
0 → 100644
View file @
32bed828
/*
* Copyright 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package
com
.
google
.
android
.
exoplayer2
.
transformer
;
import
java.nio.ByteBuffer
;
import
java.nio.ByteOrder
;
/* package */
final
class
SilentAudioGenerator
{
private
static
final
int
DEFAULT_BUFFER_SIZE
=
4096
;
private
final
ByteBuffer
internalBuffer
;
private
long
remainingBytesToOutput
;
public
SilentAudioGenerator
(
long
totalDurationUs
,
long
sampleRate
,
int
frameSize
)
{
remainingBytesToOutput
=
(
sampleRate
*
frameSize
*
totalDurationUs
)
/
1_000_000L
;
internalBuffer
=
ByteBuffer
.
allocate
(
DEFAULT_BUFFER_SIZE
).
order
(
ByteOrder
.
nativeOrder
());
internalBuffer
.
flip
();
}
public
ByteBuffer
getBuffer
()
{
if
(!
internalBuffer
.
hasRemaining
())
{
// "next" buffer.
internalBuffer
.
clear
();
if
(
remainingBytesToOutput
<
internalBuffer
.
capacity
())
{
internalBuffer
.
limit
((
int
)
remainingBytesToOutput
);
}
// Only reduce remaining bytes when we "generate" a new one.
remainingBytesToOutput
-=
internalBuffer
.
remaining
();
}
return
internalBuffer
;
}
public
boolean
isEnded
()
{
return
!
internalBuffer
.
hasRemaining
()
&&
remainingBytesToOutput
==
0
;
}
}
library/transformer/src/main/java/com/google/android/exoplayer2/transformer/Transformer.java
View file @
32bed828
...
@@ -30,6 +30,7 @@ import com.google.android.exoplayer2.C;
...
@@ -30,6 +30,7 @@ import com.google.android.exoplayer2.C;
import
com.google.android.exoplayer2.ExoPlayerLibraryInfo
;
import
com.google.android.exoplayer2.ExoPlayerLibraryInfo
;
import
com.google.android.exoplayer2.MediaItem
;
import
com.google.android.exoplayer2.MediaItem
;
import
com.google.android.exoplayer2.audio.AudioProcessor
;
import
com.google.android.exoplayer2.audio.AudioProcessor
;
import
com.google.android.exoplayer2.audio.SonicAudioProcessor
;
import
com.google.android.exoplayer2.effect.GlEffect
;
import
com.google.android.exoplayer2.effect.GlEffect
;
import
com.google.android.exoplayer2.effect.GlEffectsFrameProcessor
;
import
com.google.android.exoplayer2.effect.GlEffectsFrameProcessor
;
import
com.google.android.exoplayer2.effect.GlMatrixTransformation
;
import
com.google.android.exoplayer2.effect.GlMatrixTransformation
;
...
@@ -86,6 +87,7 @@ public final class Transformer {
...
@@ -86,6 +87,7 @@ public final class Transformer {
private
ImmutableList
<
Effect
>
videoEffects
;
private
ImmutableList
<
Effect
>
videoEffects
;
private
boolean
removeAudio
;
private
boolean
removeAudio
;
private
boolean
removeVideo
;
private
boolean
removeVideo
;
private
boolean
forceSilentAudio
;
private
ListenerSet
<
Transformer
.
Listener
>
listeners
;
private
ListenerSet
<
Transformer
.
Listener
>
listeners
;
private
MediaSource
.
@MonotonicNonNull
Factory
mediaSourceFactory
;
private
MediaSource
.
@MonotonicNonNull
Factory
mediaSourceFactory
;
private
Codec
.
DecoderFactory
decoderFactory
;
private
Codec
.
DecoderFactory
decoderFactory
;
...
@@ -124,6 +126,7 @@ public final class Transformer {
...
@@ -124,6 +126,7 @@ public final class Transformer {
this
.
videoEffects
=
transformer
.
videoEffects
;
this
.
videoEffects
=
transformer
.
videoEffects
;
this
.
removeAudio
=
transformer
.
removeAudio
;
this
.
removeAudio
=
transformer
.
removeAudio
;
this
.
removeVideo
=
transformer
.
removeVideo
;
this
.
removeVideo
=
transformer
.
removeVideo
;
this
.
forceSilentAudio
=
transformer
.
forceSilentAudio
;
this
.
listeners
=
transformer
.
listeners
;
this
.
listeners
=
transformer
.
listeners
;
this
.
mediaSourceFactory
=
transformer
.
mediaSourceFactory
;
this
.
mediaSourceFactory
=
transformer
.
mediaSourceFactory
;
this
.
decoderFactory
=
transformer
.
decoderFactory
;
this
.
decoderFactory
=
transformer
.
decoderFactory
;
...
@@ -415,6 +418,33 @@ public final class Transformer {
...
@@ -415,6 +418,33 @@ public final class Transformer {
}
}
/**
/**
* Sets whether to force silent audio for the output file, ignoring any existing audio.
*
* <p>This method is experimental and may be removed or changed without warning.
*
* <p>Audio properties/format:
*
* <ul>
* <li>Duration will match duration of the input media.
* <li>Sample mime type will match {@link TransformationRequest#audioMimeType}, or {@link
* MimeTypes#AUDIO_AAC} if {@code null}.
* <li>Sample rate will be 44100hz. This can be modified by passing a {@link
* SonicAudioProcessor} to {@link #setAudioProcessors(List)}, using {@link
* SonicAudioProcessor#setOutputSampleRateHz(int)}.
* <li>Channel count will be 2. This can be modified by implementing a custom {@link
* AudioProcessor} and passing it to {@link #setAudioProcessors(List)}.
* </ul>
*
* @param forceSilentAudio Whether to output silent audio for the output file.
* @return This builder.
*/
@CanIgnoreReturnValue
public
Builder
experimentalSetForceSilentAudio
(
boolean
forceSilentAudio
)
{
this
.
forceSilentAudio
=
forceSilentAudio
;
return
this
;
}
/**
* Builds a {@link Transformer} instance.
* Builds a {@link Transformer} instance.
*
*
* @throws IllegalStateException If both audio and video have been removed (otherwise the output
* @throws IllegalStateException If both audio and video have been removed (otherwise the output
...
@@ -443,6 +473,7 @@ public final class Transformer {
...
@@ -443,6 +473,7 @@ public final class Transformer {
videoEffects
,
videoEffects
,
removeAudio
,
removeAudio
,
removeVideo
,
removeVideo
,
forceSilentAudio
,
listeners
,
listeners
,
mediaSourceFactory
,
mediaSourceFactory
,
decoderFactory
,
decoderFactory
,
...
@@ -555,6 +586,7 @@ public final class Transformer {
...
@@ -555,6 +586,7 @@ public final class Transformer {
private
final
ImmutableList
<
Effect
>
videoEffects
;
private
final
ImmutableList
<
Effect
>
videoEffects
;
private
final
boolean
removeAudio
;
private
final
boolean
removeAudio
;
private
final
boolean
removeVideo
;
private
final
boolean
removeVideo
;
private
final
boolean
forceSilentAudio
;
private
final
ListenerSet
<
Transformer
.
Listener
>
listeners
;
private
final
ListenerSet
<
Transformer
.
Listener
>
listeners
;
private
final
MediaSource
.
Factory
mediaSourceFactory
;
private
final
MediaSource
.
Factory
mediaSourceFactory
;
private
final
FrameProcessor
.
Factory
frameProcessorFactory
;
private
final
FrameProcessor
.
Factory
frameProcessorFactory
;
...
@@ -572,7 +604,8 @@ public final class Transformer {
...
@@ -572,7 +604,8 @@ public final class Transformer {
ImmutableList
<
Effect
>
videoEffects
,
ImmutableList
<
Effect
>
videoEffects
,
boolean
removeAudio
,
boolean
removeAudio
,
boolean
removeVideo
,
boolean
removeVideo
,
ListenerSet
<
Transformer
.
Listener
>
listeners
,
boolean
forceSilentAudio
,
ListenerSet
<
Listener
>
listeners
,
MediaSource
.
Factory
mediaSourceFactory
,
MediaSource
.
Factory
mediaSourceFactory
,
Codec
.
DecoderFactory
decoderFactory
,
Codec
.
DecoderFactory
decoderFactory
,
Codec
.
EncoderFactory
encoderFactory
,
Codec
.
EncoderFactory
encoderFactory
,
...
@@ -581,6 +614,10 @@ public final class Transformer {
...
@@ -581,6 +614,10 @@ public final class Transformer {
Looper
looper
,
Looper
looper
,
DebugViewProvider
debugViewProvider
,
DebugViewProvider
debugViewProvider
,
Clock
clock
)
{
Clock
clock
)
{
if
(
forceSilentAudio
)
{
removeAudio
=
true
;
}
checkState
(!
removeVideo
||
!
forceSilentAudio
,
"Silent only audio track needs a video track."
);
checkState
(!
removeAudio
||
!
removeVideo
,
"Audio and video cannot both be removed."
);
checkState
(!
removeAudio
||
!
removeVideo
,
"Audio and video cannot both be removed."
);
this
.
context
=
context
;
this
.
context
=
context
;
this
.
transformationRequest
=
transformationRequest
;
this
.
transformationRequest
=
transformationRequest
;
...
@@ -588,6 +625,7 @@ public final class Transformer {
...
@@ -588,6 +625,7 @@ public final class Transformer {
this
.
videoEffects
=
videoEffects
;
this
.
videoEffects
=
videoEffects
;
this
.
removeAudio
=
removeAudio
;
this
.
removeAudio
=
removeAudio
;
this
.
removeVideo
=
removeVideo
;
this
.
removeVideo
=
removeVideo
;
this
.
forceSilentAudio
=
forceSilentAudio
;
this
.
listeners
=
listeners
;
this
.
listeners
=
listeners
;
this
.
mediaSourceFactory
=
mediaSourceFactory
;
this
.
mediaSourceFactory
=
mediaSourceFactory
;
this
.
decoderFactory
=
decoderFactory
;
this
.
decoderFactory
=
decoderFactory
;
...
@@ -726,6 +764,7 @@ public final class Transformer {
...
@@ -726,6 +764,7 @@ public final class Transformer {
videoEffects
,
videoEffects
,
removeAudio
,
removeAudio
,
removeVideo
,
removeVideo
,
forceSilentAudio
,
mediaSourceFactory
,
mediaSourceFactory
,
decoderFactory
,
decoderFactory
,
encoderFactory
,
encoderFactory
,
...
...
library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformerInternal.java
View file @
32bed828
...
@@ -97,6 +97,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
...
@@ -97,6 +97,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
private
final
TransformationRequest
transformationRequest
;
private
final
TransformationRequest
transformationRequest
;
private
final
ImmutableList
<
AudioProcessor
>
audioProcessors
;
private
final
ImmutableList
<
AudioProcessor
>
audioProcessors
;
private
final
ImmutableList
<
Effect
>
videoEffects
;
private
final
ImmutableList
<
Effect
>
videoEffects
;
private
final
boolean
forceSilentAudio
;
private
final
Codec
.
DecoderFactory
decoderFactory
;
private
final
Codec
.
DecoderFactory
decoderFactory
;
private
final
Codec
.
EncoderFactory
encoderFactory
;
private
final
Codec
.
EncoderFactory
encoderFactory
;
private
final
FrameProcessor
.
Factory
frameProcessorFactory
;
private
final
FrameProcessor
.
Factory
frameProcessorFactory
;
...
@@ -114,6 +115,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
...
@@ -114,6 +115,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
@Nullable
private
DecoderInputBuffer
pendingInputBuffer
;
@Nullable
private
DecoderInputBuffer
pendingInputBuffer
;
private
boolean
isDrainingPipelines
;
private
boolean
isDrainingPipelines
;
private
int
silentSamplePipelineIndex
;
private
@Transformer
.
ProgressState
int
progressState
;
private
@Transformer
.
ProgressState
int
progressState
;
private
long
progressPositionMs
;
private
long
progressPositionMs
;
private
long
durationUs
;
private
long
durationUs
;
...
@@ -131,6 +133,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
...
@@ -131,6 +133,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
ImmutableList
<
Effect
>
videoEffects
,
ImmutableList
<
Effect
>
videoEffects
,
boolean
removeAudio
,
boolean
removeAudio
,
boolean
removeVideo
,
boolean
removeVideo
,
boolean
forceSilentAudio
,
MediaSource
.
Factory
mediaSourceFactory
,
MediaSource
.
Factory
mediaSourceFactory
,
Codec
.
DecoderFactory
decoderFactory
,
Codec
.
DecoderFactory
decoderFactory
,
Codec
.
EncoderFactory
encoderFactory
,
Codec
.
EncoderFactory
encoderFactory
,
...
@@ -145,6 +148,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
...
@@ -145,6 +148,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
this
.
transformationRequest
=
transformationRequest
;
this
.
transformationRequest
=
transformationRequest
;
this
.
audioProcessors
=
audioProcessors
;
this
.
audioProcessors
=
audioProcessors
;
this
.
videoEffects
=
videoEffects
;
this
.
videoEffects
=
videoEffects
;
this
.
forceSilentAudio
=
forceSilentAudio
;
this
.
decoderFactory
=
decoderFactory
;
this
.
decoderFactory
=
decoderFactory
;
this
.
encoderFactory
=
encoderFactory
;
this
.
encoderFactory
=
encoderFactory
;
this
.
frameProcessorFactory
=
frameProcessorFactory
;
this
.
frameProcessorFactory
=
frameProcessorFactory
;
...
@@ -168,6 +172,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
...
@@ -168,6 +172,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
componentListener
,
componentListener
,
clock
);
clock
);
samplePipelines
=
new
ArrayList
<>();
samplePipelines
=
new
ArrayList
<>();
silentSamplePipelineIndex
=
C
.
INDEX_UNSET
;
dequeueBufferConditionVariable
=
new
ConditionVariable
();
dequeueBufferConditionVariable
=
new
ConditionVariable
();
muxerWrapper
=
muxerWrapper
=
new
MuxerWrapper
(
new
MuxerWrapper
(
...
@@ -258,6 +263,10 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
...
@@ -258,6 +263,10 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
while
(
samplePipeline
.
processData
())
{}
while
(
samplePipeline
.
processData
())
{}
pendingInputBuffer
=
samplePipeline
.
dequeueInputBuffer
();
pendingInputBuffer
=
samplePipeline
.
dequeueInputBuffer
();
dequeueBufferConditionVariable
.
open
();
dequeueBufferConditionVariable
.
open
();
if
(
forceSilentAudio
)
{
while
(
samplePipelines
.
get
(
silentSamplePipelineIndex
).
processData
())
{}
}
}
}
private
void
queueInputInternal
(
int
samplePipelineIndex
)
throws
TransformationException
{
private
void
queueInputInternal
(
int
samplePipelineIndex
)
throws
TransformationException
{
...
@@ -391,6 +400,11 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
...
@@ -391,6 +400,11 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
trackRegistered
=
true
;
trackRegistered
=
true
;
muxerWrapper
.
registerTrack
();
muxerWrapper
.
registerTrack
();
fallbackListener
.
registerTrack
();
fallbackListener
.
registerTrack
();
if
(
forceSilentAudio
)
{
muxerWrapper
.
registerTrack
();
fallbackListener
.
registerTrack
();
}
}
}
@Override
@Override
...
@@ -410,6 +424,23 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
...
@@ -410,6 +424,23 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
int
samplePipelineIndex
=
tracksAddedCount
;
int
samplePipelineIndex
=
tracksAddedCount
;
tracksAddedCount
++;
tracksAddedCount
++;
if
(
forceSilentAudio
)
{
Format
silentAudioFormat
=
new
Format
.
Builder
()
.
setSampleMimeType
(
MimeTypes
.
AUDIO_AAC
)
.
setSampleRate
(
44100
)
.
setChannelCount
(
2
)
.
build
();
SamplePipeline
audioSamplePipeline
=
getSamplePipeline
(
silentAudioFormat
,
streamStartPositionUs
,
streamOffsetUs
);
internalHandler
.
obtainMessage
(
MSG_REGISTER_SAMPLE_PIPELINE
,
audioSamplePipeline
)
.
sendToTarget
();
silentSamplePipelineIndex
=
tracksAddedCount
;
tracksAddedCount
++;
}
return
new
SamplePipelineInput
(
samplePipelineIndex
,
samplePipeline
.
expectsDecodedData
());
return
new
SamplePipelineInput
(
samplePipelineIndex
,
samplePipeline
.
expectsDecodedData
());
}
}
...
@@ -459,6 +490,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
...
@@ -459,6 +490,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
streamOffsetUs
,
streamOffsetUs
,
transformationRequest
,
transformationRequest
,
audioProcessors
,
audioProcessors
,
forceSilentAudio
?
durationUs
:
C
.
TIME_UNSET
,
encoderFactory
,
encoderFactory
,
muxerWrapper
,
muxerWrapper
,
/* listener= */
this
,
/* listener= */
this
,
...
@@ -509,6 +541,10 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
...
@@ -509,6 +541,10 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
if
(!
audioProcessors
.
isEmpty
())
{
if
(!
audioProcessors
.
isEmpty
())
{
return
true
;
return
true
;
}
}
if
(
forceSilentAudio
)
{
return
true
;
}
return
false
;
return
false
;
}
}
...
...
library/transformer/src/test/java/com/google/android/exoplayer2/transformer/SilentAudioGeneratorTest.java
0 → 100644
View file @
32bed828
/*
* Copyright 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package
com
.
google
.
android
.
exoplayer2
.
transformer
;
import
static
com
.
google
.
common
.
truth
.
Truth
.
assertThat
;
import
androidx.test.ext.junit.runners.AndroidJUnit4
;
import
java.nio.ByteBuffer
;
import
org.junit.Test
;
import
org.junit.runner.RunWith
;
/** Unit tests for {@link SilentAudioGenerator}. */
@RunWith
(
AndroidJUnit4
.
class
)
public
class
SilentAudioGeneratorTest
{
@Test
public
void
numberOfBytesProduced_isCorrect
()
{
SilentAudioGenerator
generator
=
new
SilentAudioGenerator
(
/* totalDurationUs= */
3_000_000
,
/* sampleRate= */
88_200
,
/* frameSize= */
12
);
int
bytesOutput
=
0
;
while
(!
generator
.
isEnded
())
{
ByteBuffer
output
=
generator
.
getBuffer
();
bytesOutput
+=
output
.
remaining
();
// "Consume" buffer.
output
.
position
(
output
.
limit
());
}
// 88_200 * 12 * 3s = 3175200
assertThat
(
bytesOutput
).
isEqualTo
(
3_175_200
);
}
@Test
public
void
lastBufferProduced_isCorrectSize
()
{
SilentAudioGenerator
generator
=
new
SilentAudioGenerator
(
/* totalDurationUs= */
1_000_000
,
/* sampleRate= */
44_100
,
/* frameSize= */
4
);
int
currentBufferSize
=
0
;
while
(!
generator
.
isEnded
())
{
ByteBuffer
output
=
generator
.
getBuffer
();
currentBufferSize
=
output
.
remaining
();
// "Consume" buffer.
output
.
position
(
output
.
limit
());
}
// Last buffer is smaller and only outputs the 'leftover' bytes.
// (44_100 * 4) % 4096 = 272
assertThat
(
currentBufferSize
).
isEqualTo
(
272
);
}
@Test
public
void
totalBytesLowerThanDefaultBufferSize_smallBufferProduced
()
{
SilentAudioGenerator
generator
=
new
SilentAudioGenerator
(
/* totalDurationUs= */
5_000
,
/* sampleRate= */
48_000
,
/* frameSize= */
4
);
// 5_000 * 48_000 * 4 / 1_000_000 = 960
assertThat
(
generator
.
getBuffer
().
remaining
()).
isEqualTo
(
960
);
}
}
library/transformer/src/test/java/com/google/android/exoplayer2/transformer/TransformerEndToEndTest.java
View file @
32bed828
...
@@ -266,6 +266,21 @@ public final class TransformerEndToEndTest {
...
@@ -266,6 +266,21 @@ public final class TransformerEndToEndTest {
}
}
@Test
@Test
public
void
startTransformation_silentAudio_completesSuccessfully
()
throws
Exception
{
Transformer
transformer
=
createTransformerBuilder
(
/* enableFallback= */
false
)
.
experimentalSetForceSilentAudio
(
true
)
.
build
();
MediaItem
mediaItem
=
MediaItem
.
fromUri
(
ASSET_URI_PREFIX
+
FILE_AUDIO_VIDEO
);
transformer
.
startTransformation
(
mediaItem
,
outputPath
);
TransformerTestRunner
.
runUntilCompleted
(
transformer
);
DumpFileAsserts
.
assertOutput
(
context
,
testMuxer
,
getDumpFileName
(
FILE_AUDIO_VIDEO
+
".silentaudio"
));
}
@Test
public
void
startTransformation_withMultipleListeners_callsEachOnCompletion
()
throws
Exception
{
public
void
startTransformation_withMultipleListeners_callsEachOnCompletion
()
throws
Exception
{
Transformer
.
Listener
mockListener1
=
mock
(
Transformer
.
Listener
.
class
);
Transformer
.
Listener
mockListener1
=
mock
(
Transformer
.
Listener
.
class
);
Transformer
.
Listener
mockListener2
=
mock
(
Transformer
.
Listener
.
class
);
Transformer
.
Listener
mockListener2
=
mock
(
Transformer
.
Listener
.
class
);
...
...
testdata/src/test/assets/transformerdumps/mp4/sample.mp4.silentaudio.dump
0 → 100644
View file @
32bed828
format 0:
id = 1
sampleMimeType = video/avc
codecs = avc1.64001F
maxInputSize = 36722
width = 1080
height = 720
frameRate = 29.970028
initializationData:
data = length 29, hash 4746B5D9
data = length 10, hash 7A0D0F2B
format 1:
sampleMimeType = audio/mp4a-latm
channelCount = 2
sampleRate = 44100
pcmEncoding = 2
sample:
trackIndex = 1
dataHashCode = 1742602241
size = 4096
isKeyFrame = true
presentationTimeUs = 0
sample:
trackIndex = 1
dataHashCode = 1742602241
size = 4096
isKeyFrame = true
presentationTimeUs = 23220
sample:
trackIndex = 1
dataHashCode = 1742602241
size = 4096
isKeyFrame = true
presentationTimeUs = 46440
sample:
trackIndex = 1
dataHashCode = 1742602241
size = 4096
isKeyFrame = true
presentationTimeUs = 69660
sample:
trackIndex = 1
dataHashCode = 1742602241
size = 4096
isKeyFrame = true
presentationTimeUs = 92880
sample:
trackIndex = 1
dataHashCode = 1742602241
size = 4096
isKeyFrame = true
presentationTimeUs = 116100
sample:
trackIndex = 1
dataHashCode = 1742602241
size = 4096
isKeyFrame = true
presentationTimeUs = 139320
sample:
trackIndex = 1
dataHashCode = 1742602241
size = 4096
isKeyFrame = true
presentationTimeUs = 162540
sample:
trackIndex = 1
dataHashCode = 1742602241
size = 4096
isKeyFrame = true
presentationTimeUs = 185760
sample:
trackIndex = 1
dataHashCode = 1742602241
size = 4096
isKeyFrame = true
presentationTimeUs = 208980
sample:
trackIndex = 1
dataHashCode = 1742602241
size = 4096
isKeyFrame = true
presentationTimeUs = 232200
sample:
trackIndex = 1
dataHashCode = 1742602241
size = 4096
isKeyFrame = true
presentationTimeUs = 255420
sample:
trackIndex = 1
dataHashCode = 1742602241
size = 4096
isKeyFrame = true
presentationTimeUs = 278640
sample:
trackIndex = 1
dataHashCode = 1742602241
size = 4096
isKeyFrame = true
presentationTimeUs = 301860
sample:
trackIndex = 1
dataHashCode = 1742602241
size = 4096
isKeyFrame = true
presentationTimeUs = 325080
sample:
trackIndex = 1
dataHashCode = 1742602241
size = 4096
isKeyFrame = true
presentationTimeUs = 348300
sample:
trackIndex = 1
dataHashCode = 1742602241
size = 4096
isKeyFrame = true
presentationTimeUs = 371520
sample:
trackIndex = 1
dataHashCode = 1742602241
size = 4096
isKeyFrame = true
presentationTimeUs = 394740
sample:
trackIndex = 1
dataHashCode = 1742602241
size = 4096
isKeyFrame = true
presentationTimeUs = 417960
sample:
trackIndex = 1
dataHashCode = 1742602241
size = 4096
isKeyFrame = true
presentationTimeUs = 441180
sample:
trackIndex = 1
dataHashCode = 1742602241
size = 4096
isKeyFrame = true
presentationTimeUs = 464400
sample:
trackIndex = 1
dataHashCode = 1742602241
size = 4096
isKeyFrame = true
presentationTimeUs = 487620
sample:
trackIndex = 1
dataHashCode = 1742602241
size = 4096
isKeyFrame = true
presentationTimeUs = 510840
sample:
trackIndex = 0
dataHashCode = -770308242
size = 36692
isKeyFrame = true
presentationTimeUs = 0
sample:
trackIndex = 0
dataHashCode = -732087136
size = 5312
isKeyFrame = false
presentationTimeUs = 66733
sample:
trackIndex = 1
dataHashCode = 1742602241
size = 4096
isKeyFrame = true
presentationTimeUs = 534059
sample:
trackIndex = 1
dataHashCode = 1742602241
size = 4096
isKeyFrame = true
presentationTimeUs = 557279
sample:
trackIndex = 1
dataHashCode = 1742602241
size = 4096
isKeyFrame = true
presentationTimeUs = 580499
sample:
trackIndex = 0
dataHashCode = 468156717
size = 599
isKeyFrame = false
presentationTimeUs = 33366
sample:
trackIndex = 0
dataHashCode = 1150349584
size = 7735
isKeyFrame = false
presentationTimeUs = 200200
sample:
trackIndex = 1
dataHashCode = 1742602241
size = 4096
isKeyFrame = true
presentationTimeUs = 603719
sample:
trackIndex = 1
dataHashCode = 1742602241
size = 4096
isKeyFrame = true
presentationTimeUs = 626939
sample:
trackIndex = 1
dataHashCode = 1742602241
size = 4096
isKeyFrame = true
presentationTimeUs = 650159
sample:
trackIndex = 1
dataHashCode = 1742602241
size = 4096
isKeyFrame = true
presentationTimeUs = 673379
sample:
trackIndex = 1
dataHashCode = 1742602241
size = 4096
isKeyFrame = true
presentationTimeUs = 696599
sample:
trackIndex = 1
dataHashCode = 1742602241
size = 4096
isKeyFrame = true
presentationTimeUs = 719819
sample:
trackIndex = 0
dataHashCode = 1443582006
size = 987
isKeyFrame = false
presentationTimeUs = 133466
sample:
trackIndex = 0
dataHashCode = -310585145
size = 673
isKeyFrame = false
presentationTimeUs = 100100
sample:
trackIndex = 0
dataHashCode = 807460688
size = 523
isKeyFrame = false
presentationTimeUs = 166833
sample:
trackIndex = 0
dataHashCode = 1936487090
size = 6061
isKeyFrame = false
presentationTimeUs = 333666
sample:
trackIndex = 1
dataHashCode = 1742602241
size = 4096
isKeyFrame = true
presentationTimeUs = 743039
sample:
trackIndex = 1
dataHashCode = 1742602241
size = 4096
isKeyFrame = true
presentationTimeUs = 766259
sample:
trackIndex = 1
dataHashCode = 1742602241
size = 4096
isKeyFrame = true
presentationTimeUs = 789479
sample:
trackIndex = 1
dataHashCode = 1742602241
size = 4096
isKeyFrame = true
presentationTimeUs = 812699
sample:
trackIndex = 1
dataHashCode = 1742602241
size = 4096
isKeyFrame = true
presentationTimeUs = 835919
sample:
trackIndex = 0
dataHashCode = -32297181
size = 992
isKeyFrame = false
presentationTimeUs = 266933
sample:
trackIndex = 0
dataHashCode = 1529616406
size = 623
isKeyFrame = false
presentationTimeUs = 233566
sample:
trackIndex = 0
dataHashCode = 1949198785
size = 421
isKeyFrame = false
presentationTimeUs = 300300
sample:
trackIndex = 0
dataHashCode = -147880287
size = 4899
isKeyFrame = false
presentationTimeUs = 433766
sample:
trackIndex = 1
dataHashCode = 1742602241
size = 4096
isKeyFrame = true
presentationTimeUs = 859139
sample:
trackIndex = 1
dataHashCode = 1742602241
size = 4096
isKeyFrame = true
presentationTimeUs = 882359
sample:
trackIndex = 1
dataHashCode = 1742602241
size = 4096
isKeyFrame = true
presentationTimeUs = 905579
sample:
trackIndex = 1
dataHashCode = 1742602241
size = 4096
isKeyFrame = true
presentationTimeUs = 928799
sample:
trackIndex = 1
dataHashCode = 1742602241
size = 4096
isKeyFrame = true
presentationTimeUs = 952019
sample:
trackIndex = 0
dataHashCode = 1369083472
size = 568
isKeyFrame = false
presentationTimeUs = 400400
sample:
trackIndex = 0
dataHashCode = 965782073
size = 620
isKeyFrame = false
presentationTimeUs = 367033
sample:
trackIndex = 0
dataHashCode = -261176150
size = 5450
isKeyFrame = false
presentationTimeUs = 567233
sample:
trackIndex = 1
dataHashCode = 1742602241
size = 4096
isKeyFrame = true
presentationTimeUs = 975239
sample:
trackIndex = 1
dataHashCode = 1742602241
size = 4096
isKeyFrame = true
presentationTimeUs = 998459
sample:
trackIndex = 1
dataHashCode = -1029274849
size = 409
isKeyFrame = true
presentationTimeUs = 1021679
sample:
trackIndex = 0
dataHashCode = -1830836678
size = 1051
isKeyFrame = false
presentationTimeUs = 500500
sample:
trackIndex = 0
dataHashCode = 1767407540
size = 874
isKeyFrame = false
presentationTimeUs = 467133
sample:
trackIndex = 0
dataHashCode = 918440283
size = 781
isKeyFrame = false
presentationTimeUs = 533866
sample:
trackIndex = 0
dataHashCode = -1408463661
size = 4725
isKeyFrame = false
presentationTimeUs = 700700
sample:
trackIndex = 0
dataHashCode = 1569455924
size = 1022
isKeyFrame = false
presentationTimeUs = 633966
sample:
trackIndex = 0
dataHashCode = -1723778407
size = 790
isKeyFrame = false
presentationTimeUs = 600600
sample:
trackIndex = 0
dataHashCode = 1578275472
size = 610
isKeyFrame = false
presentationTimeUs = 667333
sample:
trackIndex = 0
dataHashCode = 1989768395
size = 2751
isKeyFrame = false
presentationTimeUs = 834166
sample:
trackIndex = 0
dataHashCode = -1215674502
size = 745
isKeyFrame = false
presentationTimeUs = 767433
sample:
trackIndex = 0
dataHashCode = -814473606
size = 621
isKeyFrame = false
presentationTimeUs = 734066
sample:
trackIndex = 0
dataHashCode = 498370894
size = 505
isKeyFrame = false
presentationTimeUs = 800800
sample:
trackIndex = 0
dataHashCode = -1051506468
size = 1268
isKeyFrame = false
presentationTimeUs = 967633
sample:
trackIndex = 0
dataHashCode = -1025604144
size = 880
isKeyFrame = false
presentationTimeUs = 900900
sample:
trackIndex = 0
dataHashCode = -913586520
size = 530
isKeyFrame = false
presentationTimeUs = 867533
sample:
trackIndex = 0
dataHashCode = 1340459242
size = 568
isKeyFrame = false
presentationTimeUs = 934266
released = true
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment