02-05-06 FFmpeg音视频处理

1. 概述

FFmpeg是开源的音视频处理工具集,支持几乎所有音视频格式的编解码、转码、剪辑、滤镜处理。在Android开发中,通过JNI调用FFmpeg可实现强大的音视频处理能力。

1.1 FFmpeg架构

libavformat  - 封装格式处理(MP4/MKV/FLV)
libavcodec   - 编解码器(H.264/AAC)
libavfilter  - 滤镜处理(水印/美颜)
libswscale   - 视频缩放/色彩转换
libswresample- 音频重采样
libavutil    - 工具库

2. Android集成FFmpeg

2.1 编译FFmpeg

# 编译脚本 build_ffmpeg.sh
#!/bin/bash

NDK=/path/to/ndk
API=21
ARCH=arm64-v8a

./configure \
  --prefix=./android/$ARCH \
  --enable-cross-compile \
  --target-os=android \
  --arch=aarch64 \
  --cpu=armv8-a \
  --cc=$NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android$API-clang \
  --enable-shared \
  --disable-static \
  --disable-doc \
  --disable-ffmpeg \
  --disable-ffplay \
  --disable-ffprobe \
  --enable-small \
  --enable-jni \
  --enable-mediacodec \
  --enable-decoder=h264_mediacodec

make clean
make -j8
make install

2.2 JNI封装

// FFmpegNative.kt - FFmpeg JNI接口
object FFmpegNative {
    init {
        System.loadLibrary("ffmpeg-jni")
    }

    external fun executeCommand(command: Array<String>): Int
    external fun getFFmpegVersion(): String
    external fun cancelExecution()
}

// 使用示例
class FFmpegHelper {
    fun convertVideo(inputPath: String, outputPath: String) {
        val command = arrayOf(
            "ffmpeg",
            "-i", inputPath,
            "-c:v", "libx264",
            "-preset", "medium",
            "-crf", "23",
            "-c:a", "aac",
            "-b:a", "128k",
            outputPath
        )

        val result = FFmpegNative.executeCommand(command)
        if (result == 0) {
            Log.d(TAG, "转码成功")
        } else {
            Log.e(TAG, "转码失败")
        }
    }
}

3. 常用功能

3.1 视频转码

class VideoTranscoder {
    // H.265转H.264
    fun convertH265ToH264(input: String, output: String) {
        val cmd = arrayOf(
            "ffmpeg", "-i", input,
            "-c:v", "libx264",
            "-preset", "fast",
            "-crf", "22",
            "-c:a", "copy",
            output
        )
        FFmpegNative.executeCommand(cmd)
    }

    // 压缩视频
    fun compressVideo(input: String, output: String, targetSizeMB: Int) {
        // 计算目标码率
        val duration = getVideoDuration(input)
        val targetBitrate = (targetSizeMB * 8 * 1024 / duration).toInt()

        val cmd = arrayOf(
            "ffmpeg", "-i", input,
            "-b:v", "${targetBitrate}k",
            "-maxrate", "${targetBitrate * 1.5}k",
            "-bufsize", "${targetBitrate * 2}k",
            output
        )
        FFmpegNative.executeCommand(cmd)
    }
}

3.2 视频剪辑

class VideoEditor {
    // 截取视频片段
    fun trimVideo(input: String, output: String, startTime: String, duration: String) {
        val cmd = arrayOf(
            "ffmpeg", "-ss", startTime,
            "-i", input,
            "-t", duration,
            "-c", "copy",
            output
        )
        FFmpegNative.executeCommand(cmd)
    }

    // 合并视频
    fun mergeVideos(inputFiles: List<String>, output: String) {
        // 生成concat列表文件
        val listFile = File.createTempFile("concat", ".txt")
        listFile.writeText(inputFiles.joinToString("\n") { "file '$it'" })

        val cmd = arrayOf(
            "ffmpeg",
            "-f", "concat",
            "-safe", "0",
            "-i", listFile.absolutePath,
            "-c", "copy",
            output
        )
        FFmpegNative.executeCommand(cmd)

        listFile.delete()
    }
}

3.3 滤镜处理

class VideoFilter {
    // 添加水印
    fun addWatermark(input: String, watermark: String, output: String) {
        val cmd = arrayOf(
            "ffmpeg", "-i", input,
            "-i", watermark,
            "-filter_complex", "overlay=10:10",
            output
        )
        FFmpegNative.executeCommand(cmd)
    }

    // 视频旋转
    fun rotateVideo(input: String, output: String, degrees: Int) {
        val transpose = when (degrees) {
            90 -> "transpose=1"
            180 -> "transpose=2,transpose=2"
            270 -> "transpose=2"
            else -> return
        }

        val cmd = arrayOf(
            "ffmpeg", "-i", input,
            "-vf", transpose,
            output
        )
        FFmpegNative.executeCommand(cmd)
    }

    // 视频缩放
    fun scaleVideo(input: String, output: String, width: Int, height: Int) {
        val cmd = arrayOf(
            "ffmpeg", "-i", input,
            "-vf", "scale=$width:$height",
            output
        )
        FFmpegNative.executeCommand(cmd)
    }
}

4. 高级应用

4.1 提取音视频流

class StreamExtractor {
    // 提取视频流(无音频)
    fun extractVideo(input: String, output: String) {
        val cmd = arrayOf(
            "ffmpeg", "-i", input,
            "-vcodec", "copy",
            "-an",  // 无音频
            output
        )
        FFmpegNative.executeCommand(cmd)
    }

    // 提取音频流
    fun extractAudio(input: String, output: String) {
        val cmd = arrayOf(
            "ffmpeg", "-i", input,
            "-vn",  // 无视频
            "-acodec", "copy",
            output
        )
        FFmpegNative.executeCommand(cmd)
    }
}

4.2 音视频合成

class MediaMuxer {
    // 合成音视频
    fun muxAudioVideo(videoFile: String, audioFile: String, output: String) {
        val cmd = arrayOf(
            "ffmpeg",
            "-i", videoFile,
            "-i", audioFile,
            "-c:v", "copy",
            "-c:a", "aac",
            "-strict", "experimental",
            output
        )
        FFmpegNative.executeCommand(cmd)
    }

    // 替换音频
    fun replaceAudio(videoFile: String, newAudioFile: String, output: String) {
        val cmd = arrayOf(
            "ffmpeg",
            "-i", videoFile,
            "-i", newAudioFile,
            "-c:v", "copy",
            "-c:a", "aac",
            "-map", "0:v:0",
            "-map", "1:a:0",
            "-shortest",
            output
        )
        FFmpegNative.executeCommand(cmd)
    }
}

4.3 生成缩略图

class ThumbnailGenerator {
    fun generateThumbnail(videoPath: String, outputPath: String, timeSeconds: Int) {
        val cmd = arrayOf(
            "ffmpeg",
            "-ss", timeSeconds.toString(),
            "-i", videoPath,
            "-vframes", "1",
            "-vf", "scale=320:-1",
            outputPath
        )
        FFmpegNative.executeCommand(cmd)
    }

    // 生成多个缩略图
    fun generateMultipleThumbnails(videoPath: String, outputDir: String, count: Int) {
        val duration = getVideoDuration(videoPath)
        val interval = duration / (count + 1)

        for (i in 1..count) {
            val time = interval * i
            val outputPath = "$outputDir/thumb_$i.jpg"
            generateThumbnail(videoPath, outputPath, time.toInt())
        }
    }
}

5. 性能优化

5.1 硬件加速

class HardwareAccelerator {
    // 使用MediaCodec硬件编码
    fun encodeWithMediaCodec(input: String, output: String) {
        val cmd = arrayOf(
            "ffmpeg", "-i", input,
            "-c:v", "h264_mediacodec",  // 使用MediaCodec
            "-b:v", "2M",
            output
        )
        FFmpegNative.executeCommand(cmd)
    }
}

5.2 多线程处理

class BatchProcessor {
    private val executor = Executors.newFixedThreadPool(4)

    fun processBatch(files: List<String>, operation: (String) -> Unit) {
        files.forEach { file ->
            executor.submit {
                operation(file)
            }
        }
    }
}

总结

FFmpeg是音视频处理的瑞士军刀,本文涵盖:

  1. Android集成:编译与JNI封装
  2. 基础功能:转码、剪辑、滤镜
  3. 高级应用:流提取、音视频合成、缩略图生成
  4. 性能优化:硬件加速、多线程

关键要点:

  • 通过JNI调用FFmpeg C API
  • 命令行方式灵活但性能略低
  • 硬件加速显著提升性能

下一篇预告:《OpenGL ES视频渲染》

Logo

火山引擎视频云技术社区,是面向 AI 音视频开发者的技术交流平台。这里汇聚源自抖音、豆包等亿级 DAU 产品的 RTC、直播、点播、AI 媒体处理、音视频互动技术,提供接入指南、最佳实践、性能调优、场景案例、Demo 代码、开源项目、白皮书和 API 文档。社区汇聚官方工程师与一线开发者,为 AI 视频通话、数字人、AI 视频处理等应用的开发与落地提供技术支持。

更多推荐