用MediaCodec实现多段视音频的截取与拼接

视音频编辑中,对多段媒体素材进行截取和拼接是非常常见的操作。截取和拼接实际上是对媒体文件数据重新进行组合的过程。

    要实现这些功能,就需要对媒体文件进行编解码操作,即先解码要处理的媒体文件数据,然后再按照某种规则对这些数据进行编码,以生成我们所需的目标。



    Android提供的MediaCodec及其相关类为我们提供了所需的方法,这些类主要包括:MediaCodec、MediaExtractor、MediaMuxer、MediaFormat。

    MediaCodec用于创建视音频编解码器,通过它可以对视音频数据进行编解码操作,它是编解码功能的核心类。

    MediaExtractor相当于一个reader,它用于读取媒体文件,并提取出其中的视音频数据。

    MediaMuxer相当于一个writer,它用于将内存中的视音频数据写到文件中。

    MediaFormat即媒体格式类,它用于描述媒体的格式参数,如视频帧率、音频采样率等。

    对视音频文件进行截取与拼接的主要过程是:先创建视音频编解码器,再分别启动视频线程和音频线程,以分别对视音频数据进行解码、编码,最后将编码好的数据写入文件。

    主要逻辑如下,关键是三处线程同步的地方,具体原因稍后解释。


    再来了解一下这几个类的“脾气”。

    一般用MediaCodec创建编解码器时,使用的都是createDecoderByType方法,但在用三星PAD(API LEVEL 18, Android version 4.3)调试时发现,调用该方法创建音频编码器时会出错。故改为使用createByCodecName("OMX.SEC.aac.enc")创建音频编码器。估计这是一个API bug。

    configure decoder时,第一个格式参数要与源的格式相同,这里可以先通过extractor将源的格式读出来,再直接传给configure方法。configure encoder时,需设定几个必要的参数,具体请参考官方说明。

    当把全部要处理的数据灌给编解码器后,使用queueInputBuffer(inputBufIndex,0, 0, 0L, MediaCodec.BUFFER_FLAG_END_OF_STREAM)方法来通知编解码器结束工作。之后,在OutputBuffer的bufferInfo中,将携带这个标志,通过判断是否有这个标志,我们就可以断定数据是否都已处理完成,以进行后续操作。再之后,dequeueOutputBuffer的返回都将是INFO_TRY_AGAIN_LATER。当然,如果你自己有办法在OutputBuffer中判断数据是否灌完,也可以不使用此标志。

    当数据灌完后,要使用releaseOutputBuffer,把缓冲区释放掉。否则,你会发现queueInputBuffer总是返回-1,因为没有空闲的缓冲区了。

    MediaExtractor用来读取源文件,给定文件路径后,便可将其中的视音频数据拿出来。关键是它的seekTo方法,它用于对读取数据的游标进行定位,可以定位到指定点前的最后一个sync点、指定点后的第一个sync点,或者与指定点最近的sync点。对于H.264数据,sync点可以认为是视频关键帧的时码位置。

    MediaMuxer的使用非常简单,先创建,再添加视音频轨,然后start,再writeSampleData,最后stop。需要注意的是,必需先添加完所有的视音频轨后,再去start,而不能先start,再试图去addTrack,否则会出错。这也是为什么在本文的逻辑中,视频线程需要去wait音频线程添加完音频轨后再继续的原因。

    需要注意的是,不能在启动完编解码器后,立即调用getOutputFormat企图addTrack,而应该在dequeueOutputBuffer后的INFO_OUTPUT_FORMAT_CHANGED中调用,否则会出错。

    当要向文件中同时写入视频和音频数据时,必需先writeSampleData所有视频数据,再写音频数据,或者反之,即二者必需连续调用writeSampleData,不能交叉调用,否则写出的文件会有问题。这也是本文中为何muxer启动后,音频线程需等待视频线程先写完数据,自己才能继续干活的原因。

    当所有的writeSampleData完成后,不要忘记调用stop和release,否则写出的文件也会有问题。

    最后还要注意,这几个类中,时间单位都是微秒,不要搞错了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Android MediaCodec 可以用于屏幕录制和音频录制,但是它们需要分别处理。 对于屏幕录制,您需要创建一个 MediaProjection 对象,然后使用它来创建一个 VirtualDisplay 对象来捕获屏幕内容。然后,您可以使用 MediaCodec 将屏幕捕获的数据编码为视频文件格式。 对于音频录制,您需要使用 AudioRecord 对象来捕获音频数据。然后,您可以使用 MediaCodec音频数据编码为音频文件格式。 为了同时录制屏幕和音频,您需要将编码器的输出合并到一个文件中。您可以使用 MediaMuxer 类来完成此操作。您可以使用 MediaMuxer 将视频文件和音频文件合并为一个文件。 这是一个基本的示例代码,可以录制屏幕和音频: ``` MediaProjectionManager manager = (MediaProjectionManager) getSystemService(Context.MEDIA_PROJECTION_SERVICE); MediaProjection projection = manager.getMediaProjection(resultCode, data); MediaCodec videoCodec = MediaCodec.createEncoderByType("video/avc"); MediaCodec audioCodec = MediaCodec.createEncoderByType("audio/mp4a-latm"); MediaMuxer muxer = new MediaMuxer(outputFile, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4); // Create VirtualDisplay for screen capture VirtualDisplay display = projection.createVirtualDisplay("ScreenCapture", screenWidth, screenHeight, screenDensity, DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR, surface, null, null); // Create AudioRecord for audio capture AudioRecord audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC, sampleRate, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT, bufferSize); // Start recording videoCodec.start(); audioCodec.start(); audioRecord.startRecording(); // Start encoding while (isRecording) { // Encode video and audio data ByteBuffer videoData = // get video data from surface ByteBuffer audioData = // get audio data from AudioRecord videoCodec.queueInputBuffer(inputBufferIndex, 0, videoData.length(), timestamp, flags); audioCodec.queueInputBuffer(inputBufferIndex, 0, audioData.length(), timestamp, flags); // Get output from encoders MediaCodec.BufferInfo videoBufferInfo = new MediaCodec.BufferInfo(); MediaCodec.BufferInfo audioBufferInfo = new MediaCodec.BufferInfo(); int videoOutputIndex = videoCodec.dequeueOutputBuffer(videoBufferInfo, TIMEOUT_US); int audioOutputIndex = audioCodec.dequeueOutputBuffer(audioBufferInfo, TIMEOUT_US); if (videoOutputIndex >= 0) { ByteBuffer videoOutput = videoCodec.getOutputBuffer(videoOutputIndex); muxer.writeSampleData(videoTrackIndex, videoOutput, videoBufferInfo); videoCodec.releaseOutputBuffer(videoOutputIndex, false); } if (audioOutputIndex >= 0) { ByteBuffer audioOutput = audioCodec.getOutputBuffer(audioOutputIndex); muxer.writeSampleData(audioTrackIndex, audioOutput, audioBufferInfo); audioCodec.releaseOutputBuffer(audioOutputIndex, false); } // Wait for next frame timestamp += 1000000 / frameRate; } // Stop recording audioRecord.stop(); audioRecord.release(); videoCodec.stop(); videoCodec.release(); audioCodec.stop(); audioCodec.release(); muxer.stop(); muxer.release(); ``` 请注意,此示例代码仅用于演示录制屏幕和音频的基本思路,实际实现可能需要更多的代码和处理。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值