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

Jetpack Media3의 Transformer API는 미디어 편집의 성능과 안정성을 향상할 수 있도록 설계되었습니다. Transformer는 다음을 비롯한 다양한 작업을 지원합니다.

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

이 페이지에서는 변환기에서 다루는 주요 사용 사례를 살펴봅니다. 자세한 내용은 Media3 Transformer의 전체 가이드를 참고하세요.

시작하기

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

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

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

중요한 클래스

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

출력 구성

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

형식 간 트랜스코딩

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

Kotlin

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

Java

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

입력 미디어 형식이 이미 오디오 또는 동영상의 변환 요청과 일치하는 경우, Transformer는 압축된 샘플을 입력 컨테이너에서 출력 컨테이너로 복사하는 과정 없이 자동으로 트랜스머싱으로 전환됩니다. 이렇게 하면 같은 형식으로 디코딩 및 재인코딩할 때 계산 비용과 잠재적 품질 손실이 방지됩니다.

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 동영상을 수정할 수 있습니다. HDR_MODE_KEEP_HDR는 다음 코드와 같이 Composition 객체를 빌드할 때 기본 모드입니다.

Kotlin

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

Java

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()

Java

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()

Java

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를 사용하여 재생 후 처음 1초 동안 동영상이 프레임을 채우도록 확대합니다.

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()

Java

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()
    }

Java

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()

Java

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)

Java

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리스너를 연결하여 완료 또는 오류 이벤트에 대한 알림을 받을 수도 있습니다.