Android拦截其它播放声音:内录音,外录音,录屏,剪辑,混音,一键制作大片全搞定

2,365 阅读14分钟

Android 多媒体,音视频领域,虽然还是在应用层开发领域,但是要熟练掌握它,并不容易,因为它不仅仅是需要掌握好涉及到的相关api就能完全理解了,需要理解数字音频知识,数字图像视频知识,还需要对音视频的编解码规则掌握好才能算是彻底理解。

(一)如何拦截其他Android应用程序播放器的原始音频数据自定义保存下来?
(二)Android拦截其它播放声音:内录音,外录音,录屏,剪辑,混音,一键制作大片全搞定

8ffe85718554479e812b20076e6a0e4e.webp

一、前言

作为大学“双编”专业研究方向出来的,所谓双编:既编曲,又编程,对音乐、音频制作编辑相比于单独程序员有独到的理解。
想当年作曲,编曲后的录音混音制作 Samplitude, Cubase, Nuendo, FL Studio, Sonar, Cool edit 等知名的音乐制作软件,及相关采样器,合成器,混响,过滤器,MIDI制作都能轻松驾驭,这些制作软件都是基于PC版的,大多数是Windows和Mac版的,大多数大学教学还是用的 Sonar

然儿,互联网科技已经发从10多年前慢慢发展到了移动端,这些PC端的软件,也是那些程序大佬和音乐大佬一起开发出来的,虽说制作音乐软件,以及数字MIDI音乐无法从PC端完全迁移到手机端。但是,近几年来流行的短视频,音频,这些简单的处理,可以在手机上面开发出相关的软件出来。

本文重点从编程的角度,简单讲解Android下面: 内录音,外录音,录屏,剪辑,混音,一键制作大片全

二、内、外录音

1. 内录音: 我在前面文章已经讲过,可以参考如何拦截其他Android应用程序播放器的原始音频数据自定义保存下来?
2. 外录音: 即是录制MIC的环境声音:与内录音唯一不同的是,内录音是捕获系统声音,外录音是录MIC环境中声音:区别如下:AudioRecord的创建方式里面写法不一样,传入了:MediaRecorder.AudioSource.MIC

audioRecord = new AudioRecord(
        MediaRecorder.AudioSource.MIC,//传入捕获声音源头是MIC :麦克风
        SAMPLE_RATE_INHZ, CHANNEL_CONFIG, AUDIO_FORMAT, minBufferSize);
audioRecord.startRecording();

3. 录音读到的数据拿去交给Android原生MediaCodec进行编码成AAC格式音频文件,和内录后续处理是一样的。参考我前面文章

三、录屏

1. 录屏: 需要先拿到录屏权限,我在前面文章也已经讲过,可以参考如何拦截其他Android应用程序播放器的原始音频数据自定义保存下来?
2.录制视频需要用到MediaRecorder: :
是Android平台上用于录制音频和视频的高级API,它封装了音频和视频的采集、编码和封装逻辑,最终生成多媒体容器格式的文件,如mp4、ogg等 ,它是内部帮你实现了硬编码到h264或者和h265的视频编码。 基本使用方法:
1). ‌创建MediaRecorder对象‌:首先需要创建一个MediaRecorder对象。
2). ‌设置音频源‌:通过调用setAudioSource()方法设置音频源,通常使用MediaRecorder.AudioSource.MIC(麦克风)。
3). ‌设置输出格式‌:使用setOutputFormat()方法设置输出格式,例如MediaRecorder.OutputFormat.MPEG_4
4). ‌设置音频编码器‌:通过setAudioEncoder()方法设置音频编码器,例如MediaRecorder.AudioEncoder.AAC
5). ‌设置输出文件‌:使用setOutputFile()方法指定输出文件的路径和名称。
6). ‌准备录制‌:调用prepare()方法准备录制。
7). ‌开始录制‌:调用start()方法开始录制。
8). ‌停止录制‌:调用stop()方法停止录制。
9). ‌释放资源‌:调用release()方法释放资源‌
3.录屏代码实现: 先配置录制视频参数及准备录屏

 //配置录制视频参数及准备录屏
    fun prepareVideo(): Boolean {
        try {
            mediaRecorder = (if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) MediaRecorder(context) else MediaRecorder()).apply {
                setVideoSource(MediaRecorder.VideoSource.SURFACE)  // 设置录制的视频源为屏幕
                setOutputFormat(MediaRecorder.OutputFormat.MPEG_4)// 设置输出文件的格式
                setOutputFile(videoOutputFile.getAbsolutePath())// 设置输出文件路径
                setVideoEncoder(MediaRecorder.VideoEncoder.H264)// 设置视频编码 H264
                setVideoSize(mScreenWidth, mScreenHeight) // 设置视频的分辨率
                setVideoEncodingBitRate(mScreenWidth * mScreenHeight)// 设置视频比特率
//                setCaptureRate(VIDEO_CAPTURE_RATE) // 设置延时摄影帧率为3FPS
                setVideoFrameRate(VIDEO_FRAME_RATE)// 设置视频的帧率   30帧    一个I帧
            }
            mediaRecorder?.run {
                prepare()
                virtualDisplay = mediaProjection?.createVirtualDisplay(
                    "WXScreenVideoRecorder", mScreenWidth, mScreenHeight, mScreenDensity, DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR, surface, null, null
                )
            }
            return true
        } catch (e: Exception) {
            e.printStackTrace()
            return false
        }
    }

4.开始录屏

//开始录屏
fun startRecordVideo() {
    mediaRecorder?.start()
}

5.停止录屏

//停止录屏
fun stopRecordVideo() {
    try {
        mediaRecorder?.apply {
            setOnErrorListener(null)
            setOnInfoListener(null)
            setPreviewDisplay(null)
            stop()
        }
    } catch (e: Exception) {
        e.printStackTrace()
    } finally {
        mediaRecorder?.reset()
        virtualDisplay?.release()
    }
}

四、剪辑

1. 剪辑音频: 不管是剪辑音频AAC格式还是mp3格式,都是需要从压缩的音频格式里面提取出来PCM格式来进行处理:
2. 简单音频格式介绍:

音频格式说明
·WAV一种无压缩的音频格式,它以线性脉冲编码调制(PCM)方式存储音频数据。WAV文件通常比其他格式的文件大,但具有高质量的声音效果。
·MP3一种有损压缩的音频格式,它通过删除音频信号中不必要的部分来减小文件大小,从而使得传输和存储更加方便。MP3是最常见的音频格式之一,因为它的压缩率较高,同时保持了良好的音质
·AAC一种有损压缩的音频格式,主要用于移动设备和网络流媒体应用。与MP3相比,AAC提供了更好的音质和更高的压缩率。
·FLAC一种无损压缩的音频格式,它可以将音频文件压缩到原始文件大小的一半或更少,同时保留完整的音频信息。FLAC通常被音乐发烧友使用,因为它提供了高质量的音频效果,同时不会丢失任何重要的细节
·OGG一种自由、开放、无损和有损的音频格式,它采用了基金会的技术。OGG通常用于网络流媒体应用,因为它可以提供高质量的音频效果,并且可以快速传输
·PCM一种将模拟信号数字化的技术,广泛应用于音频和视频信号的编码。PCM格式通过对模拟信号进行采样、量化和编码,将其转换为数字信号,以便在数字媒介上进行存储、传输和处理

3.媒体提取器:MediaExtractor是Android系统中的一个媒体解封装库,主要用于从视频或音频文件中提取媒体轨道(如视频、音频、字幕等)的数据。其主要功能是将多媒体文件中的音频、视频等数据分离出来,并提供给应用程序进行解码、处理和播放‌

主要使用以下几个步骤:
1). ‌打开媒体文件‌: 通过setDataSource方法打开媒体文件,可以是本地文件或网络文件。
2). ‌识别媒体文件格式‌: MediaExtractor会读取媒体文件的头部信息,识别其格式。
3). ‌创建Extractor‌: 根据媒体文件的格式,MediaExtractor会创建对应的Extractor(如MP4Extractor等),用于读取媒体文件中的音频和视频数据。
4). ‌提取数据‌:通过readSampleData方法将分离出来的数据读取到ByteBuffer中,供后续处理使用‌。

4. 剪辑音频代码实现: 通过MediaExtractor来提取出里面的数据,它可以指定到起始为止,从原始音频数据中提取出pcm数据。

/**
 * 从文件中提取出音频 并输出成 PCM 格式
 *
 * @param inputPath :文件输入目录
 * @param outPath   : 输出的PCM 文件目录
 * @param startTime :截取的开始时间
 * @param endTime   : 截取的结束时间
 * @throws Exception :抛出的异常
 */
@SuppressLint("WrongConstant")
public static void decodeToPCM(String inputPath, String outPath, int startTime, int endTime) throws Exception {
    if (endTime < startTime) {
        return;
    }
    // 准备媒体提取器
    MediaExtractor mediaExtractor = new MediaExtractor();
    // 设值路径
    mediaExtractor.setDataSource(inputPath);
    // 找到音频索引
    int audioTrack = selectTrack(mediaExtractor, true);
    // 选择轨道
    mediaExtractor.selectTrack(audioTrack);
    // 指定到开始位置
    mediaExtractor.seekTo(startTime, MediaExtractor.SEEK_TO_NEXT_SYNC);
    // 拿到音频配置
    MediaFormat audioFormat = mediaExtractor.getTrackFormat(audioTrack);
    // 创建媒体解码器
    MediaCodec mediaCodec = MediaCodec.createDecoderByType(audioFormat.getString((MediaFormat.KEY_MIME)));
    mediaCodec.configure(audioFormat, null, null, 0);
    mediaCodec.start();
    int maxBufferSize = 100 * 1000;
    if (audioFormat.containsKey(MediaFormat.KEY_MAX_INPUT_SIZE)) {
        maxBufferSize = audioFormat.getInteger(MediaFormat.KEY_MAX_INPUT_SIZE);
    } else {
        maxBufferSize = 100 * 1000;
    }
    //输出的 pcm 格式文件
    File pcmFile = new File(outPath);
    FileChannel writeChannel = new FileOutputStream(pcmFile).getChannel();
    ByteBuffer buffer = ByteBuffer.allocateDirect(maxBufferSize);
    MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
    while (true) {
        int inIndex = mediaCodec.dequeueInputBuffer(1000);
        if (inIndex >= 0) {
            //获取到       视频容器  里面读取的当前时间戳
            long sampleTimeUs = mediaExtractor.getSampleTime();
            if (sampleTimeUs == -1) {
                //已经读到了末尾
                break;
            } else if (sampleTimeUs < startTime) {
                //丢弃的意思
                mediaExtractor.advance();
            } else if (sampleTimeUs > endTime) {
                break;
            }
            // 此处需要 通过 mediaExtractor 提取器 一帧一帧提取
            info.size = mediaExtractor.readSampleData(buffer, 0);
            info.presentationTimeUs = sampleTimeUs;
            info.flags = mediaExtractor.getSampleFlags();

            byte[] content = new byte[buffer.remaining()];
            buffer.get(content);
            // 将数据 给到 dsp 芯片 进行解码
            ByteBuffer inputBuffer = mediaCodec.getInputBuffer(inIndex);
            inputBuffer.put(content);
            mediaCodec.queueInputBuffer(inIndex, 0, info.size, info.presentationTimeUs, info.flags);
            // 释放上一帧的压缩数据
            mediaExtractor.advance();
        }

        /**   以下为 DSP芯片已经解码完成,DSP 芯片输出 ***/
        int outIndex = -1;
        outIndex = mediaCodec.dequeueOutputBuffer(info, 1_000);
        if (outIndex >= 0) {
            ByteBuffer decodeOutputBuffer = mediaCodec.getOutputBuffer(outIndex);
            // 得到解码后 的  原始数据  写入到输出 pcm 文件
            writeChannel.write(decodeOutputBuffer);
            mediaCodec.releaseOutputBuffer(outIndex, false);
        }
    }

    writeChannel.close();
    mediaExtractor.release();
    mediaCodec.stop();
    mediaCodec.release();
}


/**
 * 找出视频文件中 音频轨
 *
 * @param extractor : 媒体提取器
 * @param audio     :是否是音频轨
 * @return 返回轨道索引
 */
public static int selectTrack(MediaExtractor extractor, boolean audio) {
    int numTracks = extractor.getTrackCount();
    for (int i = 0; i < numTracks; i++) {
        // 轨道配置信息, 码流读取 ,sps  pps 解析
        MediaFormat format = extractor.getTrackFormat(i);
        // 轨道类型
        String mime = format.getString(MediaFormat.KEY_MIME);
        if (audio) {
            if (mime.startsWith("audio")) {
                return i;
            }
        } else {
            if (mime.startsWith("video")) {
                return i;
            }
        }

    }
    return -1;
}


5. 剪辑视频
需要从视频里面提先提取出音频,和视频,然后剪辑到开始和结束位置

剪辑视频里面音频数据:和上面剪辑音频代码是一样的

剪辑视频里的视频数据: 如下

// 视频
if (audioTrack >= 0) {
    // 切换轨道
    mediaExtractor.unselectTrack(audioTrack);
}
// 切换到视频轨
mediaExtractor.selectTrack(videoIndex);
// seek到 startTimeUs 时间戳的  前一个I帧
mediaExtractor.seekTo(startTimeUs, MediaExtractor.SEEK_TO_PREVIOUS_SYNC);
//视频 最大帧 最大的那一个帧   大小
maxBufferSize = videoFormat.getInteger(MediaFormat.KEY_MAX_INPUT_SIZE);
buffer = ByteBuffer.allocateDirect(maxBufferSize);
while (true) {
    long sampleTimeUs = mediaExtractor.getSampleTime();
    if (sampleTimeUs == -1) {
        // 提取器读取结束
        break;
    }
    if (sampleTimeUs < startTimeUs) {
        mediaExtractor.advance();
        continue;
    }
    if (endTimeUs != null && sampleTimeUs > endTimeUs) {
        break;
    }
    info.presentationTimeUs = sampleTimeUs - startTimeUs + 600;
    info.flags = mediaExtractor.getSampleFlags();
    // 提取器读取出来到 buffer
    info.size = mediaExtractor.readSampleData(buffer, 0);
    if (info.size < 0) {
        // 提取器里
        break;
    }
    //写入视频数据
    mediaMuxer.writeSampleData(muxerVideoIndex, buffer, info);
    mediaExtractor.advance();
}

五、混音

混音涉及到多个音频进行处理:比如需要将2个声音混到一起:
从物理学上讲,声音量化成数字信号是一条波形,2条波形混到一起,就是把两条波形数据相加,但是相加得到的结果新的声音波形,它的振幅即声音不能超过数字信号的最大最小值范围,这里的范围是 -32768-32767,超过了就是刺耳的电流噪音了。

比如:两条PCM音频数据进行混合:

/**
 * 将两个PCM 原始音频数据 混音
 *
 * @param pcm1Path : pcm文件1 的路径
 * @param pcm2Path : pcm文件2 的路径
 * @param toPath   : 混音后pcm文件路径
 * @param vol1     :音量1
 * @param vol2     :音量2
 * @throws IOException
 */
public static void mixPcm(String pcm1Path, String pcm2Path, String toPath, int vol1, int vol2) throws IOException {
    float volume1 = vol1 / 100f * 1; //先转成float 防止 精度丢失
    float volume2 = vol2 / 100f * 1; //先转成float 防止 精度丢失

    //待混音的两条数据流
    FileInputStream is1 = new FileInputStream(pcm1Path);
    FileInputStream is2 = new FileInputStream(pcm2Path);
    //输出的数据流
    FileOutputStream fileOutputStream = new FileOutputStream(toPath);
    boolean end1 = false, end2 = false;
    byte[] buffer1 = new byte[2048]; //声音1读取 2个字节
    byte[] buffer2 = new byte[2048]; //声音2读取 2个字节
    byte[] buffer3 = new byte[2048]; //混合后声音 2个字节
    short temp2, temp1;//short 个 16位
    while (!end1 || !end2) {
        if (!end2) {
            //是否读取到结束
            end2 = (is2.read(buffer2) == -1);
        }
        if (!end1) {
            //是否读取到结束
            end1 = (is1.read(buffer1) == -1);
        }
        // 混合后声音 音量
        int voice = 0;
        for (int i = 0; i < buffer2.length; i += 2) {
            //前面低8位 低字节    后面高8位字节
            temp1 = (short) ((buffer1[i] & 0xff) | (buffer1[i + 1] & 0xff) << 8);
            temp2 = (short) ((buffer2[i] & 0xff) | (buffer2[i + 1] & 0xff) << 8);

            voice = (int) (temp1 * volume1 + temp2 * volume2);
            if (voice > 32767) {
                voice = 32767;
            } else if (voice < -32768) {
                voice = -32768;
            }
            //相加后 还原 成 前面低8位 低字节    后面高8位字节
            buffer3[i] = (byte) (voice & 0xFF);
            buffer3[i + 1] = (byte) ((voice >>> 8) & 0xFF);
        }
        // 混音相加后写出到文件
        fileOutputStream.write(buffer3);
    }
    is1.close();
    is2.close();
    fileOutputStream.close();
}

六、一键制作大片

怎么一键制作大片:
比如具体步骤:

  1. 找一条视频,里面含有人声 和 图像
  2. 找一条配置背景音乐。
  3. 选取好视频开始时间,结束时间,音频开始时间结束时间,配置好 背景音乐声音,和视频里面音频声音,以便后面混音调节叠加声音
  4. 把上面的混好制作在一起
  5. 代码实现步骤:
    1)将视频里面音频按照开始结束时间提取出pcm数据格式文件
    2)把背景音乐音频按照开始结束时间提取出pcm数据格式文件
    3)将上述2段音频数据进行叠加混音得到混音后的PCM数据文件
    4)将上述混音后的PCM音频数据处理成wav格式的音频数据文件(mediaExtractor不支持直接处理PCM格式的
    5)将视频按照开始结束位置剪辑出来,和上述两段音频混后处理好的wav格式进行混合组装通过MediaMuxer写到MP4文件里面去

完整代码如下:

private static void mixVideoAndMusic(String videoInput, String output, Integer startTimeUs, Integer endTimeUs, File wavFile) throws IOException {
    //视频容器  输出  注意支持的格式只有这么几种
    MediaMuxer mediaMuxer = new MediaMuxer(output, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
    // 准备媒体提取器
    MediaExtractor mediaExtractor = new MediaExtractor();
    // 设值路径
    mediaExtractor.setDataSource(videoInput);
    // 拿到原视频中的  视频轨 索引
    int videoIndex = selectTrack(mediaExtractor, false);
    // 拿到原视频中的  音频轨 索引
    int audioIndex = selectTrack(mediaExtractor, true);
    // 拿到原视频编码配置
    MediaFormat videoFormat = mediaExtractor.getTrackFormat(videoIndex);
    // 视频容器  输出 新添加之前视频配置
    int muxerVideoIndex = mediaMuxer.addTrack(videoFormat);
    /***                     **********************************************************                          ***/
    // 拿到原视频 中音频编码配置
    MediaFormat audioFormat = mediaExtractor.getTrackFormat(audioIndex);
    // 拿到原视频中 音频采样位数
    int audioBitrate = audioFormat.getInteger(MediaFormat.KEY_BIT_RATE);
    // 拿到原视频中 音频采样率
    int sampleRate = audioFormat.getInteger(MediaFormat.KEY_SAMPLE_RATE);
    // 拿到原视频中 音频通道数
    int channelCount = audioFormat.getInteger(MediaFormat.KEY_CHANNEL_COUNT);

    audioFormat.setString(MediaFormat.KEY_MIME, MediaFormat.MIMETYPE_AUDIO_AAC);
    // 视频容器  输出 新添加之前视频中 音频配置
    int muxerAudioIndex = mediaMuxer.addTrack(audioFormat);
    // 输出容器开始 工作
    mediaMuxer.start();

    // 准备提取之前混音好的 wav 文件 提取器
    MediaExtractor pcmExtractor = new MediaExtractor();
    // 将之前混音好的 wav 文件路径给进去
    pcmExtractor.setDataSource(wavFile.getAbsolutePath());
    // 找到 混音好的WAV 中音频 索引
    int audioTrack = selectTrack(pcmExtractor, true);
    // 选取 混音后wav 文件中 音频轨
    pcmExtractor.selectTrack(audioTrack);
    // 拿到 混音后wab 文件中的 音频pcm 编码配置
    MediaFormat pcmTrackFormat = pcmExtractor.getTrackFormat(audioTrack);
    //最大一帧的 大小
    int maxBufferSize = 0;
    if (pcmTrackFormat.containsKey(MediaFormat.KEY_MAX_INPUT_SIZE)) {
        maxBufferSize = pcmTrackFormat.getInteger(MediaFormat.KEY_MAX_INPUT_SIZE);
    } else {
        maxBufferSize = 100 * 1000;
    }
    // 要将 PCM 编码成 AAC 压缩格式 这里创建 音频 编码器配置 格式
    MediaFormat encodeFormat = MediaFormat.createAudioFormat(MediaFormat.MIMETYPE_AUDIO_AAC, sampleRate, channelCount);//参数对应-> mime type、采样率、声道数
    // 设置音频编码比特率
    encodeFormat.setInteger(MediaFormat.KEY_BIT_RATE, audioBitrate);
    // 设置音频编码音质等级
    encodeFormat.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC);
    // 设置音频编码 最大一帧大小
    encodeFormat.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, maxBufferSize);
    // 硬编编码工具 编码 音频
    MediaCodec encoder = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_AUDIO_AAC);
    encoder.configure(encodeFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
    // 开始编码
    encoder.start();
    // 准备 音频 一帧 buffer
    ByteBuffer buffer = ByteBuffer.allocateDirect(maxBufferSize);
    MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
    //是否编码完成
    boolean encodeDone = false;
    while (!encodeDone) {
        int inputBufferIndex = encoder.dequeueInputBuffer(10000);
        if (inputBufferIndex >= 0) {
            long sampleTime = pcmExtractor.getSampleTime();
            if (sampleTime < 0) {
                // 编码结束
                encoder.queueInputBuffer(inputBufferIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
            } else {
                int flags = pcmExtractor.getSampleFlags();
                int size = pcmExtractor.readSampleData(buffer, 0);
                ByteBuffer inputBuffer = encoder.getInputBuffer(inputBufferIndex);
                inputBuffer.clear();
                inputBuffer.put(buffer);
                inputBuffer.position(0);
                // 通知编码
                encoder.queueInputBuffer(inputBufferIndex, 0, size, sampleTime, flags);
                // 放弃内存,  一定要写  不写不能督导新的数据
                pcmExtractor.advance();
            }
        }

        // 输出的容器的索引 
        int outIndex = encoder.dequeueOutputBuffer(info, TIMEOUT);
        while (outIndex >= 0) {
            if (info.flags == MediaCodec.BUFFER_FLAG_END_OF_STREAM) {
                encodeDone = true;
                break;
            }
            // 通过索引 得到 编码好的数据在哪个容器
            ByteBuffer encodeOutputBuffer = encoder.getOutputBuffer(outIndex);
            //数据写进去了 ,写入到音频轨
            mediaMuxer.writeSampleData(muxerAudioIndex, encodeOutputBuffer, info);
            // 清空容器数据  方便下次读
            encodeOutputBuffer.clear();
            // 把编码器的数据释放 ,方便dsp 下一帧存
            encoder.releaseOutputBuffer(outIndex, false);
            outIndex = encoder.dequeueOutputBuffer(info, TIMEOUT);
        }
    }

    // 视频
    if (audioTrack >= 0) {
        // 切换轨道
        mediaExtractor.unselectTrack(audioTrack);
    }
    // 切换到视频轨
    mediaExtractor.selectTrack(videoIndex);
    // seek到 startTimeUs 时间戳的  前一个I帧
    mediaExtractor.seekTo(startTimeUs, MediaExtractor.SEEK_TO_PREVIOUS_SYNC);
    //视频 最大帧 最大的那一个帧   大小
    maxBufferSize = videoFormat.getInteger(MediaFormat.KEY_MAX_INPUT_SIZE);
    buffer = ByteBuffer.allocateDirect(maxBufferSize);
    while (true) {
        long sampleTimeUs = mediaExtractor.getSampleTime();
        if (sampleTimeUs == -1) {
            // 提取器读取结束
            break;
        }
        if (sampleTimeUs < startTimeUs) {
            mediaExtractor.advance();
            continue;
        }
        if (endTimeUs != null && sampleTimeUs > endTimeUs) {
            break;
        }
        info.presentationTimeUs = sampleTimeUs - startTimeUs + 600;
        info.flags = mediaExtractor.getSampleFlags();
        // 提取器读取出来到 buffer
        info.size = mediaExtractor.readSampleData(buffer, 0);
        if (info.size < 0) {
            // 提取器里
            break;
        }
        //写入视频数据
        mediaMuxer.writeSampleData(muxerVideoIndex, buffer, info);
        mediaExtractor.advance();
    }
    pcmExtractor.release();
    mediaExtractor.release();
    encoder.stop();
    encoder.release();
    mediaMuxer.release();
}

七、总结:

本文重点介绍了 内录音,外录音,录屏,剪辑,混音,一键制作大片的简单实现步骤:
用到了相关android原生的组件:
MediaProjection :用于屏幕捕捉和屏幕录制
MediaCodec:用于音视频编解码的API
AudioRecord:用于录制音频的工具
MediaRecorder:实现音视频录制的功能
MediaMuxer:用来封装编码后的视频流和音频流到mp4容器中
MediaExtractor:用于从视频或音频文件中提取媒体轨道(如视频、音频、字幕等)的数据
AudioFormat:用于指定音频数据格式的类
MediaFormat:用于描述音视频格式的类
另外:如果需要更深入的对音视频编码数据理解:需要对音视频数字信号理论进行掌握。

感谢阅读:

欢迎 关注,点赞、收藏

这里你会学到不一样的东西