使用Android MediaCodec进行视频转码

时间:2019-11-25 18:18:42

标签: java android mediacodec

与Android MediaCodec斗争,我正在寻找一种简单的方法来更改Android中视频文件的分辨率。

目前,我正在尝试一种单线程转码方法,该方法可以逐步完成所有工作,因此我可以很好地理解它,并且从总体上看,它如下所示:

public void TranscodeVideo()
{
    // Extract
    MediaTrack[] tracks = ExtractTracks(InputPath);

    // Decode
    MediaTrack videoTrack = tracks.Where(o => o.IsVideo).FirstOrDefault();
    MediaTrack rawVideoTrack = DecodeTrack(videoTrack);

    // Edit?
    // ResizeVideoTrack(rawVideoTrack);

    // Encode
    MediaFormat newFormat = MediaHelper.CreateVideoOutputFormat(videoTrack.Format);
    MediaTrack encodeVideodTrack = EncodeTrack(rawVideoTrack , newFormat);

    // Muxe
    encodeVideodTrack.Index = videoTrack.Index;
    tracks[Array.IndexOf(tracks, videoTrack)] = encodeVideodTrack;
    MuxeTracks(OutputPath, tracks);
}

提取工作正常,返回仅包含音频的轨道和仅包含视频的轨道。 Muxing可以很好地结合之前的两个曲目。解码有效,但我不知道如何检查,轨道上的原始帧比原始帧重得多,因此我认为这是正确的。

问题

编码器输入缓冲区的大小小于原始帧的大小,并且还与编码配置的格式有关,因此我假设我需要以某种方式调整帧的大小,但是我没有发现任何有用的东西。我是对的吗?我想念什么吗?调整原始视频帧大小的方法是什么?有什么帮助吗? :S

PD

  • 也许您会注意到我正在使用C#(Xamarin.Android)以获得更多乐趣。但是底层的API当然是Java。
  • 我使用的是ByteBuffers,而不是Surfaces,因为它看起来更容易。我将是使用曲面的下一步,欢迎任何建议。
  • 我知道单线程处理效率很低,但是很简单。将解码器输出缓冲区连接到编码器输入缓冲区的下一步是下一步。
  • 我浏览了PhilLabGrafikaBigflake的示例,但似乎没有什么对我有用。
  • 避免在Android上使用ffmpeg。

感谢大家的宝贵时间。

2 个答案:

答案 0 :(得分:1)

从上面的注释开始实施libVLC

将此添加到应用程序根目录的build.gradle

allprojects {
    repositories {
       ...
       maven {
           url 'https://jitpack.io'
       }
   }
}

将此添加到依赖应用程序的build.gradle

dependancies {
    ...
    implementation 'com.github.masterwok:libvlc-android-sdk:3.0.13'
}

以下是将RTSP流作为活动加载的示例

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.camera_stream_layout);


    // Get URL
    this.rtspUrl = getIntent().getExtras().getString(RTSP_URL);
    Log.d(TAG, "Playing back " + rtspUrl);

    this.mSurface = findViewById(R.id.camera_surface);
    this.holder = this.mSurface.getHolder();

    ArrayList<String> options = new ArrayList<>();
    options.add("-vvv"); // verbosity
    //Add vlc transcoder options here

    this.libvlc = new LibVLC(getApplicationContext(), options);
    this.holder.setKeepScreenOn(true);
    //this.holder.setFixedSize();

    // Create media player
    this.mMediaPlayer = new MediaPlayer(this.libvlc);
    this.mMediaPlayer.setEventListener(this.mPlayerListener);

    // Set up video output
    final IVLCVout vout = this.mMediaPlayer.getVLCVout();
    vout.setVideoView(this.mSurface);

    //Set size of video to fit app screen
    DisplayMetrics displayMetrics = new DisplayMetrics();
    getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);

    ViewGroup.LayoutParams videoParams = this.mSurface.getLayoutParams();
    videoParams.width = displayMetrics.widthPixels;
    videoParams.height = displayMetrics.heightPixels;

    vout.setWindowSize(videoParams.width, videoParams.height);
    vout.addCallback(this);
    vout.attachViews();

    final Media m = new Media(this.libvlc, Uri.parse(this.rtspUrl));
    //Use this to add transcoder options m.addOption("vlc transcode options here");
    this.mMediaPlayer.setMedia(m);
    this.mMediaPlayer.play();
}

这是vlc转码器选项的文档

https://wiki.videolan.org/Documentation:Streaming_HowTo_New/

答案 1 :(得分:1)

是的,编码器的输入缓冲区较小,因为它希望输入具有指定的尺寸。顾名思义,仅编码器进行编码。

我将您的问题更多地理解为“为什么”而不是“如何”问题,因此,我只会指出您在哪里可以找到“为什么”

解码后的帧是YUV图像(建议快速浏览wikipedia article),如果我没记错,但设备之间可能有所不同,通常是NV21。为此,我建议您使用一个库,因为图像的每个平面都需要不同程度地缩小,并且通常需要进行过滤。请查看libYUV。如果您对实际的尺寸调整算法感兴趣,请查看this和实现this

如果不需要使用字节缓冲区来处理解码和编码,我建议使用已经提到过的表面。与解码字节缓冲区相比,它具有多个优点。

  • 本机缓冲区和应用程序分配的缓冲区之间没有副本,内存效率更高,因为本机缓冲区只是在表面之间来回交换。
  • 如果您打算渲染框架(无论是用于调整大小还是显示),都可以通过设备图形处理器来完成。有关如何执行此操作的方法,请查看BigFlakes DecodeEditEncode测试。

希望这能回答您的一些问题。