Media3 Transformer를 사용하여 기본 동영상 편집 앱 만들기

Jetpack Media3의 Transformer API는 미디어 편집을 실적이 우수하고 안정적으로 만들도록 설계되었습니다. Transformer는 다음과 같은 다양한 작업을 지원합니다.

  • 자르기, 크기 조절, 회전을 사용하여 동영상 수정
  • 오버레이 및 필터와 같은 효과 추가
  • HDR, 슬로 모션 동영상과 같은 특수 형식 처리
  • 수정사항을 적용한 후 미디어 항목 내보내기

이 페이지에서는 Transformer에서 다루는 몇 가지 주요 사용 사례를 안내합니다. 자세한 내용은 Media3 Transformer에 관한 전체 가이드를 참고하세요.

시작하기

시작하려면 Jetpack Media3의 Transformer, Effect, Common 모듈에 종속 항목을 추가합니다.

implementation "androidx.media3:media3-transformer:1.4.1"
implementation "androidx.media3:media3-effect:1.4.1"
implementation "androidx.media3:media3-common:1.4.1"

1.4.1을 원하는 라이브러리 버전으로 바꿔야 합니다. 출시 노트에서 최신 버전을 확인할 수 있습니다.

중요 클래스

클래스 목적
Transformer 변환을 시작 및 중지하고 실행 중인 변환의 진행 상황 업데이트를 확인합니다.
EditedMediaItem 처리할 미디어 항목과 해당 항목에 적용할 수정사항을 나타냅니다.
Effects 오디오 및 동영상 효과 컬렉션입니다.

출력 구성

Transformer.Builder를 사용하면 이제 TransformationRequest 객체를 만들지 않고도 함수를 설정하여 videoMimeTypeaudioMimetype 디렉터리를 지정할 수 있습니다.

형식 간 트랜스코딩

다음 코드는 H.265/AVC 동영상 및 AAC 오디오를 출력하도록 Transformer 객체를 구성하는 방법을 보여줍니다.

Kotlin

val transformer = Transformer.Builder(context)
    .setVideoMimeType(MimeTypes.VIDEO_H265)
    .setAudioMimeType(MimeTypes.AUDIO_AAC)
    .build()

자바

Transformer transformer = new Transformer.Builder(context)
    .setVideoMimeType(MimeTypes.VIDEO_H265)
    .setAudioMimeType(MimeTypes.AUDIO_AAC)
    .build();

입력 미디어 형식이 이미 오디오 또는 동영상의 변환 요청과 일치하면 Transformer는 자동으로 transmuxing으로 전환합니다. 즉, 압축된 샘플을 입력 컨테이너에서 출력 컨테이너로 수정하지 않고 복사합니다. 이렇게 하면 동일한 형식으로 디코딩 및 다시 인코딩할 때의 계산 비용과 잠재적인 품질 손실을 방지할 수 있습니다.

HDR 모드 설정

입력 미디어 파일이 HDR 형식인 경우 Transformer가 HDR 정보를 처리하는 방법에 관한 몇 가지 모드 중에서 선택할 수 있습니다. HDR_MODE_KEEP_HDR 또는 HDR_MODE_TONE_MAP_HDR_TO_SDR_USING_OPEN_GL를 사용하는 것이 좋습니다.

HDR_MODE_KEEP_HDR HDR_MODE_TONE_MAP_HDR_TO_SDR_USING_OPEN_GL
설명 HDR 데이터를 보존합니다. 즉, HDR 출력 형식은 HDR 입력 형식과 동일합니다. OpenGL 톤 매퍼를 사용하여 HDR 입력을 SDR로 톤 매핑합니다. 즉, 출력 형식은 SDR이 됩니다.
지원 FEATURE_HdrEditing 기능이 있는 인코더가 포함된 기기의 API 수준 31 이상에서 지원됩니다. API 수준 29 이상에서 지원됩니다.
오류 지원되지 않는 경우 HDR_MODE_TONE_MAP_HDR_TO_SDR_USING_OPEN_GL를 대신 사용해 봅니다. 지원되지 않으면 ExportException이 발생합니다.

필요한 인코딩 기능을 지원하고 Android 13(API 수준 33) 이상을 실행하는 기기에서는 Transformer 객체를 사용하여 HDR 동영상을 수정할 수 있습니다. 다음 코드와 같이 Composition 객체를 빌드할 때 HDR_MODE_KEEP_HDR가 기본 모드입니다.

Kotlin

val composition = Composition.Builder(
    ImmutableList.of(videoSequence))
    .setHdrMode(HDR_MODE_KEEP_HDR)
    .build()

자바

Composition composition = new Composition.Builder(
    ImmutableList.of(videoSequence))
    .setHdrMode(Composition.HDR_MODE_KEEP_HDR)
    .build();

미디어 항목 준비하기

MediaItem는 앱의 오디오 또는 동영상 항목을 나타냅니다. EditedMediaItem는 적용할 변환과 함께 MediaItem를 수집합니다.

동영상 자르기

동영상에서 원치 않는 부분을 삭제하려면 MediaItemClippingConfiguration를 추가하여 맞춤 시작 위치와 종료 위치를 설정하면 됩니다.

Kotlin

val clippingConfiguration = MediaItem.ClippingConfiguration.Builder()
    .setStartPositionMs(10_000) // start at 10 seconds
    .setEndPositionMs(20_000) // end at 20 seconds
    .build()
val mediaItem = MediaItem.Builder()
    .setUri(videoUri)
    .setClippingConfiguration(clippingConfiguration)
    .build()

자바

ClippingConfiguration clippingConfiguration = new MediaItem.ClippingConfiguration.Builder()
    .setStartPositionMs(10_000) // start at 10 seconds
    .setEndPositionMs(20_000) // end at 20 seconds
    .build();
MediaItem mediaItem = new MediaItem.Builder()
    .setUri(videoUri)
    .setClippingConfiguration(clippingConfiguration)
    .build();

내장 효과 사용

Media3에는 일반적인 변환을 위한 여러 내장 동영상 효과가 있습니다. 예를 들면 다음과 같습니다.

클래스 결과
Presentation 해상도 또는 가로세로 비율로 미디어 항목 크기 조정
ScaleAndRotateTransformation 배율로 미디어 항목 크기 조정 또는 미디어 항목 회전
Crop 미디어 항목을 더 작거나 큰 프레임으로 자릅니다.
OverlayEffect 미디어 항목 위에 텍스트 또는 이미지 오버레이 추가

오디오 효과의 경우 원본 (PCM) 오디오 데이터를 변환하는 AudioProcessor 인스턴스 시퀀스를 추가할 수 있습니다. 예를 들어 ChannelMixingAudioProcessor를 사용하여 오디오 채널을 믹스하고 크기를 조절할 수 있습니다.

이러한 효과를 사용하려면 효과 또는 오디오 프로세서의 인스턴스를 만들고, 미디어 항목에 적용할 오디오 및 동영상 효과로 Effects 인스턴스를 빌드한 다음 Effects 객체를 EditedMediaItem에 추가합니다.

Kotlin

val channelMixingProcessor = ChannelMixingAudioProcessor()
val rotateEffect = ScaleAndRotateTransformation.Builder().setRotationDegrees(60f).build()
val cropEffect = Crop(-0.5f, 0.5f, -0.5f, 0.5f)

val effects = Effects(listOf(channelMixingProcessor), listOf(rotateEffect, cropEffect))

val editedMediaItem = EditedMediaItem.Builder(mediaItem)
    .setEffects(effects)
    .build()

자바

ChannelMixingAudioProcessor channelMixingProcessor = new ChannelMixingAudioProcessor();
ScaleAndRotateTransformation rotateEffect = new ScaleAndRotateTransformation.Builder()
    .setRotationDegrees(60f)
    .build();
Crop cropEffect = new Crop(-0.5f, 0.5f, -0.5f, 0.5f);

Effects effects = new Effects(
    ImmutableList.of(channelMixingProcessor),
    ImmutableList.of(rotateEffect, cropEffect)
);

EditedMediaItem editedMediaItem = new EditedMediaItem.Builder(mediaItem)
    .setEffects(effects)
    .build();

맞춤 효과 만들기

Media3에 포함된 효과를 확장하여 사용 사례에 맞는 맞춤 효과를 만들 수 있습니다. 다음 예에서는 서브클래스 MatrixTransformation를 사용하여 재생의 첫 번째 초 동안 프레임을 채우도록 동영상을 확대합니다.

Kotlin

val zoomEffect = MatrixTransformation { presentationTimeUs ->
    val transformationMatrix = Matrix()
    // Set the scaling factor based on the playback position
    val scale = min(1f, presentationTimeUs / 1_000f)
    transformationMatrix.postScale(/* x */ scale, /* y */ scale)
    transformationMatrix
}

val editedMediaItem = EditedMediaItem.Builder(inputMediaItem)
    .setEffects(Effects(listOf(), listOf(zoomEffect))
    .build()

자바

MatrixTransformation zoomEffect = presentationTimeUs -> {
    Matrix transformationMatrix = new Matrix();
    // Set the scaling factor based on the playback position
    float scale = min(1f, presentationTimeUs / 1_000f);
    transformationMatrix.postScale(/* x */ scale, /* y */ scale);
    return transformationMatrix;
};

EditedMediaItem editedMediaItem = new EditedMediaItem.Builder(inputMediaItem)
    .setEffects(new Effects(ImmutableList.of(), ImmutableList.of(zoomEffect)))
    .build();

효과의 동작을 추가로 맞춤설정하려면 GlShaderProgram를 구현합니다. queueInputFrame() 메서드는 입력 프레임을 처리하는 데 사용됩니다. 예를 들어 MediaPipe의 머신러닝 기능을 활용하려면 MediaPipe FrameProcessor를 사용하여 MediaPipe 그래프를 통해 각 프레임을 전송하면 됩니다. Transformer 데모 앱에서 예를 확인하세요.

효과 미리보기

ExoPlayer를 사용하면 내보내기 프로세스를 시작하기 전에 미디어 항목에 추가된 효과를 미리 볼 수 있습니다. EditedMediaItem와 동일한 Effects 객체를 사용하여 ExoPlayer 인스턴스에서 setVideoEffects()를 호출합니다.

Kotlin

val player = ExoPlayer.builder(context)
    .build()
    .also { exoPlayer ->
        exoPlayer.setMediaItem(inputMediaItem)
        exoPlayer.setVideoEffects(effects)
        exoPlayer.prepare()
    }

자바

ExoPlayer player = new ExoPlayer.builder(context).build();
player.setMediaItem(inputMediaItem);
player.setVideoEffects(effects);
exoPlayer.prepare();

ExoPlayer로 오디오 효과를 미리 볼 수도 있습니다. ExoPlayer 인스턴스를 빌드할 때 AudioProcessor 시퀀스를 사용하는 AudioSink에 오디오를 출력하도록 플레이어의 오디오 렌더러를 구성하는 맞춤 RenderersFactory를 전달합니다. 아래 예에서는 DefaultRenderersFactorybuildAudioSink() 메서드를 재정의하여 이 작업을 실행합니다.

Kotlin

val player = ExoPlayer.Builder(context, object : DefaultRenderersFactory(context) {
    override fun buildAudioSink(
        context: Context,
        enableFloatOutput: Boolean,
        enableAudioTrackPlaybackParams: Boolean,
        enableOffload: Boolean
    ): AudioSink? {
        return DefaultAudioSink.Builder(context)
            .setEnableFloatOutput(enableFloatOutput)
            .setEnableAudioTrackPlaybackParams(enableAudioTrackPlaybackParams)
            .setOffloadMode(if (enableOffload) {
                     DefaultAudioSink.OFFLOAD_MODE_ENABLED_GAPLESS_REQUIRED
                } else {
                    DefaultAudioSink.OFFLOAD_MODE_DISABLED
                })
            .setAudioProcessors(arrayOf(channelMixingProcessor))
            .build()
        }
    }).build()

자바

ExoPlayer player = new ExoPlayer.Builder(context, new DefaultRenderersFactory(context) {
        @Nullable
        @Override
        protected AudioSink buildAudioSink(
            Context context,
            boolean enableFloatOutput,
            boolean enableAudioTrackPlaybackParams,
            boolean enableOffload
        ) {
            return new DefaultAudioSink.Builder(context)
                .setEnableFloatOutput(enableFloatOutput)
                .setEnableAudioTrackPlaybackParams(enableAudioTrackPlaybackParams)
                .setOffloadMode(
                    enableOffload
                        ? DefaultAudioSink.OFFLOAD_MODE_ENABLED_GAPLESS_REQUIRED
                        : DefaultAudioSink.OFFLOAD_MODE_DISABLED)
                .setAudioProcessors(new AudioProcessor[]{channelMixingProcessor})
                .build();
        }
    }).build();

변환 시작

마지막으로 Transformer를 만들어 수정사항을 적용하고 결과 미디어 항목의 내보내기를 시작합니다.

Kotlin

val transformer = Transformer.Builder(context)
    .addListener(listener)
    .build()
transformer.start(editedMediaItem, outputPath)

자바

Transformer transformer = new Transformer.Builder(context)
    .addListener(listener)
    .build();
transformer.start(editedMediaItem, outputPath);

필요한 경우 Transformer.cancel()를 사용하여 내보내기 프로세스를 취소할 수도 있습니다.

진행 상황 업데이트 확인

Transformer.start는 즉시 반환되고 비동기식으로 실행됩니다. 변환의 현재 진행 상황을 쿼리하려면 Transformer.getProgress()를 호출합니다. 이 메서드는 ProgressHolder를 사용하며 진행 상태를 사용할 수 있는 경우, 즉 메서드가 PROGRESS_STATE_AVAILABLE를 반환하는 경우 제공된 ProgressHolder가 현재 진행률 백분율로 업데이트됩니다.

완료 또는 오류 이벤트에 관한 알림을 받기 위해 Transformer리스너를 연결할 수도 있습니다.