精华内容
下载资源
问答
  • 行业分类-电信-嵌入式音视频混合信号同步编码技术.rar
  • ffmpeg混音以及音视频混合

    千次阅读 2017-03-21 19:11:22
    Duration = MAX(input a, v) 0. 前言 本文包括以下内容:  ...(2)音视频混合——muxing  (3)修改音频音量 1. Audio merge 1.1 amerge -ac 2 speed up ffmpeg -i test.mp4 -i test.

    Duration = MAX(input a, v)

    0. 前言

    本文包括以下内容: 
    (1)混音:多个音频混合 
    采用amerge, amix对多个音频进行混音(而非拼接) 
    (2)音视频混合——muxing 
    (3)修改音频音量

    1. Audio merge

    1.1 amerge

    -ac 2 speed up

    
    ffmpeg -i test.mp4 -i test.mp3 -filter_complex "[0:a] [1:a]amerge=inputs=2[aout]" -map "[aout]" -ac 2 mix_amerge.aac
    
       
    • 1
    • 2
    • 3
    • 1
    • 2
    • 3

    PS: Without ac speed will be half.

    1.2 amix

    speed = amerge + ac 2

    
    ffmpeg -i test.aac -i test.mp3 -filter_complex amix=inputs=2:duration=first:dropout_transition=2  mix.aac
    
       
    • 1
    • 2
    • 3
    • 1
    • 2
    • 3

    2. Simple A/V Muxer

    -shortest : duration = MIN(inputs)

    
    ffmpeg -i test.mp4 -i test.mp3 -vcodec copy -acodec aac -map 0:v:0 -map 1:a:0 -shortest mix_test.mp4
    
       
    • 1
    • 2
    • 3
    • 1
    • 2
    • 3

    adjust volume

    
    ffmpeg -i test.mp4 -i test.mp3 -vcodec copy -acodec aac -map 0:v:0 -map 1:a:0 -vol 60 mix_test.mp4
       
    • 1
    • 2
    • 1
    • 2

    3. Complex A/V Merge (audio merge)

    amerge : duration is shortest

    
    ffmpeg -i test.mp4 -i test.mp3 -c:v copy -map 0:v:0 -filter_complex "[0:a][1:a]amerge=inputs=2[aout]" -map "[aout]" -ac 2  mix_amerge.mp4
    
       
    • 1
    • 2
    • 3
    • 1
    • 2
    • 3

    amix : setup duration

    ffmpeg -i test.mp4 -i test.mp3 -filter_complex "[0:a][1:a]amix=inputs=2:duration=first[aout]" -map "[aout]" -c:v copy -map 0:v:0 mix_amerge.mp4
       
    • 1
    • 1

    4. Loop shorter iterm

    concat shorter one

    ffmpeg -i input_audio -f concat -i <(for i in {1..n}; do printf "file '%s'\n" input_video; done) -c:v copy -c:a copy -shortest output_video
       
    • 1
    • 1

    5. Adjust Volume

    audio:

    ffmpeg -i test.aac -i test.mp3 -filter_complex "[0:a]aformat=sample_fmts=fltp:sample_rates=44100:channel_layouts=stereo,volume=0.9[a0]; [1:a]aformat=sample_fmts=fltp:sample_rates=44100:channel_layouts=stereo,volume=0.5[a1]; [a0][a1]amerge=inputs=2[aout]" -map "[aout]" -ac 2 mix_v0.5.aac
       
    • 1
    • 1

    video:

    ffmpeg -i test.mp4 -i test.mp3 -filter_complex "[0:a]aformat=sample_fmts=fltp:sample_rates=44100:channel_layouts=stereo,volume=0.9[a0]; [1:a]aformat=sample_fmts=fltp:sample_rates=44100:channel_layouts=stereo,volume=0.5[a1]; [a0][a1]amix=inputs=2:duration=first[aout]" -map "[aout]" -ac 2 -c:v copy -map 0:v:0 mix_amerge.mp4
       
    • 1
    • 1
    1
    0
     
     
    展开全文
  • node ffmpeg混音以及音视频混合处理

    千次阅读 2019-06-10 09:27:20
    https://www.jianshu.com/p/2a824f13b2affmpeg混音以及音视频混合处理 http://phpstudy.php.cn/c.php/45618.htmlffmpeg 视频合并 如何保留音频 https://www.cnblogs.com/laie...

    https://www.jianshu.com/p/2a824f13b2af 使用FFmpeg合并音视频

    https://www.jianshu.com/p/2a824f13b2af fmpeg混音以及音视频混合处理

    http://phpstudy.php.cn/c.php/45618.html  ffmpeg 视频合并 如何保留音频

    https://www.cnblogs.com/laien/p/7132951.html  ffmpeg nodejs中使用

    http://nodejs.cn/api/child_process.html

     

    node执行ffmpeg命令

    只需要把ffmpeg.exe放在node执行的目录下,就可以使用node来执行ffmpeg命令。
    实例代码:

    const child = require('child_process')
    child.exec("ffmpeg -i "concat:first.mp3|second.mp3" -acodec copy third.mp3", function(err){
        if (err) {
            console.log(err.message);
            info.message = err.message;
            event.sender.send(data.callback, JSON.stringify(info))
        } else {
            info.flag = true;
            info.message = '音频处理保存成功'
            event.sender.send(data.callback, JSON.stringify(info))
        }
    })
    --------------------- 
    作者:SoGreater 
    来源:CSDN 
    原文:https://blog.csdn.net/qq_36607860/article/details/86472948 
    版权声明:本文为博主原创文章,转载请附上博文链接!

     

     

    1.获取到推流地址
    2.进入cmd,输入命令ffmpeg -re -i xxxxx.flv -c copy -f flv "xxxxurl"
    其中,
    xxxxx.flv为本地视频文件,Sioeye推流需要符合使用的是视频H264,音频ACC编码
    "xxxxxrul"为推流地址

     

    ffmpe NodeJs中使用

     

    var exec = require('child_process').exec;
    var Ffmpeg = require('fluent-ffmpeg');
    var config = require('../config')
    // module.exports  = {
       function generatTsfile (activity,sourceFile) {
            // var source
            // var commandStr = 'ffmpeg -i ';
            // commandStr += config.videodirectory +  videoFile ;
            // commandStr += ' -y -vcodec copy -acodec copy -map 0 -f segment -segment_list ';
            // commandStr +=+ config.videodirectory + ''
            var commandStr = 'ffmpeg -i D:/desktop/testfile/1.flv  -y -vcodec copy -acodec copy -map 0 -f segment -segment_list D:/desktop/testfile/test1.m3u8   -segment_time 10 D:/desktop/testfile/test-%03d.ts'
    
            var command = Ffmpeg('D:/desktop/testfile/454224124c08470a90a9eaa0b3cb885a/123224124c08470a90a9eaa0b3cb6666/sourceVideo/1080p_3_2.mp4')
                .save('D:/desktop/testfile/454224124c08470a90a9eaa0b3cb885a/123224124c08470a90a9eaa0b3cb6666/ts/1080p_3_2-%03d.ts')
                .outputOptions([
                    '-y',
                '-vcodec copy',
                '-acodec copy',
                '-map 0',
                  '-f segment',
                '-segment_list D:/desktop/testfile/454224124c08470a90a9eaa0b3cb885a/123224124c08470a90a9eaa0b3cb6666/sourceVideo/1080p_3_2.m3u8',
                '-segment_time 10']
                )
    
                // .takeScreenshots({ timemarks: [ '00:00:02.000' ],
                //     size: '150x100',
                //     filename:'thumbnail-at-%s-%00i-seconds.jpg'
                // }
                //  , 'D:/desktop/testfile');
    
            command
                .on('progress', function(info) {
                    console.log('progress ' + info.percent + '%');
                })
                .on('filenames', function(filenames) {
                    console.log('screenshots are ' + filenames.join(', '));
                })
                .on('error', function(err) {
                    console.log('An error occurred: ' + err.message);
                })
                .on('end', function() {
                    console.log('Merging finished !');
                })
        }
    
    var num = 0;
    function generateImage(i) {
    
        console.log(i,num++);
        // var commandStr = 'ffmpeg -i D:/desktop/testfile/1.mp4 -f image2 -vf fps=fps=1/2 -q:v 0 D:/desktop/testfile/test-%02d.jpg';
        
        var command = Ffmpeg('D:/desktop/testfile/454224124c08470a90a9eaa0b3cb885a/123224124c08470a90a9eaa0b3cb6666/ts/1080p_3_2-'+i+'.ts')
            .save('D:/desktop/testfile/454224124c08470a90a9eaa0b3cb885a/123224124c08470a90a9eaa0b3cb6666/thumbnailtmp/1080p_3_2-'+i+'-%02d.jpg')
            .outputOptions([
                '-y',
                '-f image2',
                '-vf fps=fps=1/2',
                '-q:v 0'
            ])
           // .videoFilter('fps=fps=1/2')
           //  .takeScreenshots({
           //       //timemarks: [ '00:00:02.000' ],
           //      size: '150x100',
           //      filename:'thumbnail-at-%s-%00i-seconds.jpg'
           //  }
           //   , 'D:/desktop/testfile');
    
        command
            .on('progress', function(info) {
                //console.log('progress ' + info.percent + '%');
            })
            .on('filenames', function(filenames) {
                //console.log('screenshots are ' + filenames.join(', '));
            })
            .on('error', function(err) {
                //console.log('An error occurred: ' + err.message);
            })
            .on('end', function() {
                //console.log('Merging finished !');
            })
    
    
    }
     // generatTsfile()
    
    
    
    //
    var count = 0;
    function generate() {
       // setTimeout(function () {
    
                var str = count.toString().length === 1 ? '00'+count : '0'+count;
            generateImage(str)
            count ++;
            console.log(count,str);
            if(count<32)
                generate();
    
        // },10000)
    }
    
    generate();
    
    
    
    
    
    // //切片生成图片
    // var exec = require('child_process').exec;
    // var commandStr = 'ffmpeg -i D:/desktop/testfile/1.mp4 -f image2 -vf fps=fps=1/2 D:/desktop/testfile/test-%02d.jpg&exit';
    // var time = new Date();
    // exec(commandStr,function (err,data,data1) {
    //     console.log(new Date() - time);
    // })
    
    
    // var tsStr = 'ffmpeg -i D:/desktop/testfile/1.mp4  -c:v libx264 -c:a aac -strict -2 -f hls output.m3u8';
    //
    //生成切片
    // var generalTsStr = 'ffmpeg -i D:/desktop/testfile/1.flv  -y -vcodec copy -acodec copy -map 0 -f segment -segment_list D:/desktop/testfile/test1.m3u8   -segment_time 10 D:/desktop/testfile/test-%03d.ts'
    //
    // var time = new Date();
    // exec(generalTsStr,function (error, stdout, stderr) {
    //     console.log(new Date() - time);
    // })
    
    // var spawn = require('child_process').spawn;
    
    //Set the path to where FFmpeg is installed
    //proc.setFfmpegPath("D:\\ffmpeg-3.0\\vs2013_build\\bin\\ffmpeg.exe"); //I forgot to include "ffmpeg.exe"
    
    
    // ffmpeg('D:/desktop/testfile/1.mp4')
    //     .videoCodec('libx264')
    //     .audioCodec('libmp3lame')
    //     .size('320x240')
    //     .on('error', function(err) {
    //         console.log('An error occurred: ' + err.message);
    //     })
    //     .on('end', function() {
    //         console.log('Processing finished !');
    //     })
    //     .save('D:/desktop/testfile/output.mp4');
    
    // ffmpeg('D:/desktop/testfile/1.mp4')
    //     .on('stderr', function(stderrLine) {
    //         console.log('Stderr output: ' + stderrLine);
    //     })
    //     .on('progress', function(progress) {
    //         console.log('Processing: ' + progress.percent + '% done');
    //     })
    //     .on('error', function(err, stdout, stderr) {
    //         console.log('Cannot process video: ' + err.message);
    //     })
    //     .on('end', function(stdout, stderr) {
    //         console.log('Transcoding succeeded !');
    //     });;;
    
      // var command = ffmpeg('D:/desktop/testfile/1.mp4')
      //     .audioCodec('libfaac')
      //   .videoCodec('libx264')
      //   .format('mp4');
      //
      // command.clone()
      //   .size('320x200')
      //   .save('D:/desktop/testfile/11.mp4');
    
    
    // ffmpeg('D:/desktop/testfile/1.mp4')
    //     .screenshots({
    //         timestamps: [30.5, '50%', '00:10.123'],
    //         filename: 'thumbnail-at-%s-seconds.png',
    //         folder: 'D:/desktop/testfile/output',
    //         size: '320x240'
    //     });
    // //
    展开全文
  • 本文将介绍视频、音频处理的方法;方便大家使用 Demo:https://coding.net/u/Xoxo_x/p/VideoAndAudio/git/blob/master/AVFoundation%E9%9F%B3%E8%A7%86%E9%A2%91%E6%B7%B7%E5%90%88%E5%8E%8B%E7%BC%A9.zip或者:...

    这里写图片描述

    本文将介绍视频、音频处理的方法;方便大家使用

    Demo:https://coding.net/u/Xoxo_x/p/VideoAndAudio/git/blob/master/AVFoundation%E9%9F%B3%E8%A7%86%E9%A2%91%E6%B7%B7%E5%90%88%E5%8E%8B%E7%BC%A9.zip

    或者:https://coding.net/u/Xoxo_x/p/VideoAndAudio/git/tree/master

    首先

    音视频处理需要用到AVFoundation下的AVAssetExportSession,通过exportAsynchronouslyWithCompletionHandler回调,得知处理状况。

    一、视频压缩

    视频压缩实际上是一种视频格式的转换、苹果原生录制的视频通过此方法可以压缩为原来的1/8 ~ 1/9左右。

    • 创建AVURLAsset
            // 视频来源
            let videoInputUrl = NSURL.fileURLWithPath(NSBundle.mainBundle().pathForResource("Daid", ofType: "mp4")!)
            // 视频采集
            let videoAsset = AVURLAsset(URL: videoInputUrl, options: nil)
    
    
    • 创建输出路径
     // 最终合成输出路径
            let name = currentTimeStamp() + ".mp4"
            let temporaryFile = (NSTemporaryDirectory() as NSString).stringByAppendingPathComponent(name)
    
            print(temporaryFile)
            let outputFileUrl = NSURL(fileURLWithPath: temporaryFile)

    其中,currentTimeStamp()为时间戳字符串,防止重复。

    //时间戳
    func currentTimeStamp()->String {
        let now = NSDate()
        let dformatter = NSDateFormatter()
        dformatter.dateFormat = "yyyy年MM月dd日 HH:mm:ss"
        print("当前日期时间:\(dformatter.stringFromDate(now))")
        //当前时间的时间戳
        let timeInterval:NSTimeInterval = now.timeIntervalSince1970
        let timeStamp = Int(timeInterval)
    
        return String(timeStamp)
    
    }

    这时创建AVAssetExportSession

            // 创建一个输出
            let assetExport = AVAssetExportSession(asset: videoAsset, presetName: AVAssetExportPresetMediumQuality)
            // 输出类型
            assetExport!.outputFileType = AVFileTypeQuickTimeMovie
            // 输出地址
            assetExport!.outputURL = outputFileUrl
            // 优化
            assetExport!.shouldOptimizeForNetworkUse = true
            // 合成完毕
            assetExport!.exportAsynchronouslyWithCompletionHandler({() -> Void in
                // 回到主线程
                dispatch_async(dispatch_get_main_queue(), {() -> Void in
                    // 调用播放方法
                    self.playWithUrl(outputFileUrl)
                    let fileSize = try! NSFileManager.defaultManager().attributesOfItemAtPath(temporaryFile)[NSFileSize] as? NSNumber
                    let recordSize = (fileSize?.integerValue)!/1024
                    print(recordSize)
                })
            })
    

    视频压缩到此完毕。

    二、音视频混合

            //音视频合成
        func videoAudioMerge() {
            // 声音来源
            let audioInputUrl = NSURL.fileURLWithPath(NSBundle.mainBundle().pathForResource("五环之歌", ofType: "mp3")!)
            // 视频来源
            let videoInputUrl = NSURL.fileURLWithPath(NSBundle.mainBundle().pathForResource("Holiday", ofType: "mov")!)
            // 最终合成输出路径
            let name = currentTimeStamp() + ".mp4"
            let temporaryFile = (NSTemporaryDirectory() as NSString).stringByAppendingPathComponent(name)
    
            print(temporaryFile)
            let outputFileUrl = NSURL(fileURLWithPath: temporaryFile)
    
            // 时间起点
            let nextClistartTime = kCMTimeZero
            // 创建可变的音视频组合
            let comosition = AVMutableComposition()
    
            // 视频采集
            let videoAsset = AVURLAsset(URL: videoInputUrl, options: nil)
            // 视频时间范围
            let videoTimeRange = CMTimeRangeMake(kCMTimeZero, videoAsset.duration)
            // 视频通道 枚举 kCMPersistentTrackID_Invalid = 0
            let videoTrack = comosition.addMutableTrackWithMediaType(AVMediaTypeVideo, preferredTrackID: kCMPersistentTrackID_Invalid)
            // 视频采集通道
            let videoAssetTrack = videoAsset.tracksWithMediaType(AVMediaTypeVideo).first!
            //  把采集轨道数据加入到可变轨道之中
            do {
                try videoTrack.insertTimeRange(videoTimeRange, ofTrack: videoAssetTrack, atTime: nextClistartTime)
            }
            catch let error {
                print(error,"error")
            }
    
    
            // 声音采集
            let audioAsset = AVURLAsset(URL: audioInputUrl, options: nil)
            // 因为视频短这里就直接用视频长度了,如果自动化需要自己写判断
            let audioTimeRange = videoTimeRange
            // 音频通道
            let audioTrack = comosition.addMutableTrackWithMediaType(AVMediaTypeAudio, preferredTrackID: kCMPersistentTrackID_Invalid)
            // 音频采集通道
            let audioAssetTrack = audioAsset.tracksWithMediaType(AVMediaTypeAudio).first!
            // 加入合成轨道之中
            do {
                try audioTrack.insertTimeRange(audioTimeRange, ofTrack: audioAssetTrack, atTime: nextClistartTime)
            }
            catch let error {
                print(error,"error")
    
            }
    
    
            // 创建一个输出
            let assetExport = AVAssetExportSession(asset: comosition, presetName: AVAssetExportPresetMediumQuality)
            // 输出类型
            assetExport!.outputFileType = AVFileTypeQuickTimeMovie
            // 输出地址
            assetExport!.outputURL = outputFileUrl
            // 优化
            assetExport!.shouldOptimizeForNetworkUse = true
            // 合成完毕
            assetExport!.exportAsynchronouslyWithCompletionHandler({() -> Void in
                // 回到主线程
                dispatch_async(dispatch_get_main_queue(), {() -> Void in
                    // 调用播放方法
                    self.playWithUrl(outputFileUrl)
                    let fileSize = try! NSFileManager.defaultManager().attributesOfItemAtPath(temporaryFile)[NSFileSize] as? NSNumber
                    let recordSize = (fileSize?.integerValue)!/1024
                    print(recordSize)
                })
            })
    
        }
    

    到此音视频合并成功,具体内容见释义

    三、音频合成

    音频合成这里我用了别人的代码OC的、出处忘了。我自己做了改善

    调用方法

    //音视频合成
        func audioMerge() {
            // 声音来源1
            let audioInputPath1 = NSBundle.mainBundle().pathForResource("五环之歌", ofType: "mp3")!
            // 音频来源2
            let audioInputPath2 = NSBundle.mainBundle().pathForResource("爱的人", ofType: "mp3")!
    
            let ext = ExtAudioFileMixer()
            ext.audioInputPath1 = audioInputPath1;
            ext.audioInputPath2 = audioInputPath2;
    
            ext.process {url in
    
                self.playWithUrl(url)
            }
    
    
    
        }

    实现

    
        AVMutableComposition *composition =[AVMutableComposition composition];
        audioMixParams =[[NSMutableArray alloc]init];
    
        //本地音乐1
        NSURL *audioInputUrl1 =[NSURL fileURLWithPath:self.audioInputPath1];
        AVURLAsset *audioAsset =[AVURLAsset URLAssetWithURL:audioInputUrl1 options:nil];
        CMTime startTime =CMTimeMakeWithSeconds(0,audioAsset.duration.timescale);
        CMTime trackDuration =audioAsset.duration;
        //获取本地音乐1素材
        [self setUpAndAddAudioAtPath:audioInputUrl1 toComposition:composition start:startTime dura:trackDuration offset:CMTimeMake(0,44100)];
        //本地音乐2
        NSURL *audioInputUrl2 = [NSURL fileURLWithPath:self.audioInputPath2];
        //获取本地音乐2素材
        [self setUpAndAddAudioAtPath:audioInputUrl2 toComposition:composition start:startTime dura:trackDuration offset:CMTimeMake(0,44100)];
    
        //创建一个可变的音频混合
        AVMutableAudioMix *audioMix =[AVMutableAudioMix audioMix];
        audioMix.inputParameters =[NSArray arrayWithArray:audioMixParams];//从数组里取出处理后的音频轨道参数
    
        //创建一个输出
        AVAssetExportSession *exporter =[[AVAssetExportSession alloc]
                                         initWithAsset:composition
                                         presetName:AVAssetExportPresetAppleM4A];
        exporter.audioMix = audioMix;
        exporter.outputFileType = AVFileTypeAppleM4A;
        NSString* fileName =[NSString stringWithFormat:@"%@.m4a",@"overMix"];
        //输出路径
        NSString *exportFile =[NSString stringWithFormat:@"%@/%@",[self getLibarayPath], fileName];
        self.audioOutputPath = exportFile;
    
        if([[NSFileManager defaultManager]fileExistsAtPath:self.audioOutputPath]) {
            [[NSFileManager defaultManager]removeItemAtPath:self.audioOutputPath error:nil];
        }
        NSLog(@"输出路径===%@",self.audioOutputPath);
    
        NSURL *exportURL =[NSURL fileURLWithPath:self.audioOutputPath];
        exporter.outputURL = exportURL;
    
        [exporter exportAsynchronouslyWithCompletionHandler:^{
            int exportStatus =(int)exporter.status;
            switch (exportStatus){
                case AVAssetExportSessionStatusFailed:{
                    NSError *exportError = exporter.error;
                    NSLog(@"错误,信息: %@", exportError);
    
                    break;
                }
                case AVAssetExportSessionStatusCompleted:{
    
                    unsigned long long size = [[NSFileManager defaultManager] attributesOfItemAtPath:exportFile error:nil].fileSize;
                    NSLog(@"是否在主线程2%d,,%llu",[NSThread isMainThread],size/1024);
    
                    finished(exportURL);
                    break;
                }
            }
        }];
    
    

    到此,视频合并完成

    Demo地址:https://coding.net/u/Xoxo_x/p/VideoAndAudio/git/tree/master

    展开全文
  • android 音视频混合

    千次阅读 2017-05-10 14:55:48
    关于音视频的编辑,业界普遍使用的是FFmpeg库,但是如果要自己去编译优化得到一个稳定可使用FFmpeg库,需要花费巨大的人力和时间成本,这在我当时的情境下,并不现实。在网上查找资源时候,了解到,自从andr

    接到过这样的一个需求:给你一个视频(mp4)和一段音乐,合成一个新的视频,新的视频去掉原有的音频,而是用该音乐作为音频。看似简单的几句话,但接到这个需求的时候,真的非常头疼,其实这个真不简单。关于音视频的编辑,业界普遍使用的是FFmpeg库,但是如果要自己去编译优化得到一个稳定可使用FFmpeg库,需要花费巨大的人力和时间成本,这在我当时的情境下,并不现实。在网上查找资源时候,了解到,自从android 16之后,谷歌开始引入了音视频编解码库,并在android 18之后进行了完善,虽然稳定性上还是不足,但是在几乎每个版本更新后,都会对音视频库做一些改良,所以我决定android 的这种音视频库开始尝试。
    啰嗦多了,开始转入正题。阅读本博文之前需要对android的多媒体编解码库有一定的了解,下面是几个必须要先去了解的关键类。这几个类的链接我都是推荐的谷歌的官方文档,不建议各位朋友去网上搜这些相关的类的所谓详解,因为绝大部分杂乱无章,错漏百出,完全就是误人误事,阅读官方文档和源码才是最好的方法。
    MediaExtractor, 用于提取音视频数据,获取音视频文件的基本信息。
    Mediacodec, 是最核心的编解码库,通过正确的配置就能够对音频和视频进行编码或者解码。
    MediaMuxer,是将音视频合成器,通过MediaMuxer,将音频数据和视频数据分别写入同一个文件中的音频轨道和视频轨道,这样就生成了一个有声的视频文件。
    这三个类是android音视频编解码最基本的类。一个音视频编解码的基本流程如图:
    这里写图片描述

    下面我们实现音视频混合需求:

    import android.media.MediaCodec;
    import android.media.MediaCodec.BufferInfo;
    import android.media.MediaCodecInfo;
    import android.media.MediaExtractor;
    import android.media.MediaFormat;
    import android.media.MediaMuxer;
    import android.util.Log;
    
    import com.example.administrator.recording.common.utils.MediaUtils;
    
    import java.io.File;
    import java.io.IOException;
    import java.nio.ByteBuffer;
    
    /**
     * 音视频合成器
     */
    public class Compounder {
        private static int MP3_TYPE = 0;
        private static int AAC_TYPE = 1;
        private static int M4A_TYPE = 2;
        private static int NOT_DC_EC_TYPE = 3;//不用重新编解码
        private static int NOT_SUPPORT_TYPE = 4;//不支持类型
        private String TAG = "mylog";
        private int MAX_INPUT_SIZE = 10240;
        private int VIDEO_READ_SAMPLE_SIZE = 524288;//512 * 1024
        private int BIT_RATE = 65536; // 64 * 1024
        private int SAMPLE_RATE = 44100;// acc sample rate
        private Long TIMEOUT_US = 1000L;
    
        private String mAudioPath = "/storage/emulated/0/010/aa.mp3";
        private String mVideoPath = "/storage/emulated/0/010/aa.mp4";
        private String mDstFilePath = "/storage/emulated/0/010/compound.mp4";
    
        private MediaExtractor mVideoExtractor = new MediaExtractor();
        private MediaExtractor mAudioExtractor = new MediaExtractor();
        private MediaMuxer mMediaMuxer = null;
        private int mAudioTrackIndex = -1;
        private int mVideoTrackIndex = -1;
        private int mAudioDEType = -1;
        private long mMaxTimeStamp = 0;
    
        private MediaFormat mVideoFormat;
        private MediaFormat mAudioFormat;
        private MediaCodec mDecoder;
        private MediaCodec mEncoder;
    
        private ByteBuffer[] mDecodeInputBuffer;
        private ByteBuffer[] mDecodeOutputBuffer;
        private ByteBuffer[] mEncodeInputBuffer;
        private ByteBuffer[] mEncodeOutputBuffer;
    
        /**
         *
         * @param videoPath 源视频路径
         * @param audioPath 音频路径
         * @param audioDEType 音频类型
         * @param dstPath 目标文件路径
         */
        private Compounder(String videoPath, String audioPath, int audioDEType, String dstPath){
            mVideoPath = videoPath;
            mAudioPath = audioPath;
            mAudioDEType = audioDEType;
            mDstFilePath = dstPath;
        }
    
        /**
         *
         * @param videoPath 源视频路径
         * @param audioPath 音频路径
         * @param dstPath 目标文件路径
         * @return null || compounder
         */
        public static Compounder createCompounder(String videoPath, String audioPath, String dstPath){
            if(checkVideo(videoPath)){
                int audioEDType = checkAudio(audioPath);
                if(audioEDType != NOT_SUPPORT_TYPE)
                    return new Compounder(videoPath, audioPath, audioEDType, dstPath);
            }
            return null;
        }
    
        /**
         * 检查音频是否合法
         * @param audioPath
         * @return
         */
        private static int checkAudio(String audioPath) {
            if(audioPath != null){
                File file = new File(audioPath);
                if(file.exists() && file.isFile()){
                    if(audioPath.endsWith(".mp3")){
                        return MP3_TYPE;
                    }
                    if(audioPath.endsWith(".aac")){
                        return AAC_TYPE;
                    }
                    if(audioPath.endsWith(".m4a")){
                        return M4A_TYPE;
                    }
                }
            }
            return -1;
        }
    
        /**
         * 检查视频是否符合要求
         * @param videoPath
         * @return
         */
        private static boolean checkVideo(String videoPath) {
            if(videoPath != null && videoPath.endsWith(".mp4")){
                File file = new File(videoPath);
                if(file.exists() && file.isFile()){
                    return true;
                }
            }
            return false;
        }
    
        /**
         * 开始合成
         */
        public void start(){
            if(mAudioDEType == MP3_TYPE) {
                initDecodeAudio();
                initEncodeAudio();
                if (initMuxer()) {
                    if (handleTrack(mMediaMuxer, mVideoTrackIndex, mVideoExtractor)) {
                        decodeInputBuffer();//解码--->编码--->合成输出文件
                    }
                }
            }else if (mAudioDEType == NOT_DC_EC_TYPE){//直接合成文件
                if(initMuxer()){
                    handleTrack(mMediaMuxer, mVideoTrackIndex, mVideoExtractor);
                    handleTrack(mMediaMuxer, mAudioTrackIndex, mAudioExtractor);
                }
            }
            release();
            Log.e(TAG, "finished~~~~~~~~~~~~~~~~~~~~");
        }
    
        /**
         * 初始化混合器
         */
        private boolean initMuxer() {
            try {
                if((mVideoFormat = MediaUtils.getMediaFormat(mVideoExtractor, MediaUtils.VIDEO_TYPE, mVideoPath)) != null) {
                    mMediaMuxer = new MediaMuxer(mDstFilePath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
                    mVideoTrackIndex = mMediaMuxer.addTrack(mVideoFormat);//设置视频格式
                    Log.e(TAG, " video long is : " +  mVideoFormat.getLong(MediaFormat.KEY_DURATION));
                    mMaxTimeStamp = mVideoFormat.getLong(MediaFormat.KEY_DURATION);
                    if(mAudioDEType == NOT_DC_EC_TYPE){
                        MediaFormat audioFormat = MediaUtils.getMediaFormat(mAudioExtractor, MediaUtils.AUDIO_TYPE, mAudioPath);
                        if(audioFormat != null) {
                            mAudioTrackIndex = mMediaMuxer.addTrack(audioFormat);
                        }else{
                            return false;
                        }
                    }
                    else if(mAudioDEType == MP3_TYPE) {
                        if(mDecoder == null || mEncoder == null){
                            return false;
                        }
                        mAudioTrackIndex = mMediaMuxer.addTrack(mEncoder.getOutputFormat());//设置目标的音频格式
                    }
                    mMediaMuxer.start();
                    return true;
                }
            } catch (IOException e) {
                e.printStackTrace();
                mMediaMuxer = null;
            }
            return false;
        }
    
    
        /**
         * 获取输出的解码后的buffer
         */
        private void decodeOutputBuffer() {
            BufferInfo info = new BufferInfo();
            int outputIndex = mDecoder.dequeueOutputBuffer(info, -1);
            if (outputIndex >= 0) {
                byte[] chunk = new byte[info.size];
                mDecodeOutputBuffer[outputIndex].position(info.offset);
                mDecodeOutputBuffer[outputIndex].limit(info.offset + info.size);
                mDecodeOutputBuffer[outputIndex].get(chunk);
                mDecodeOutputBuffer[outputIndex].clear();
                mDecoder.releaseOutputBuffer(outputIndex, false);
                if (chunk.length > 0) {
                    encodData(chunk, info.presentationTimeUs);
                }
            }
        }
    
        /**
         * 编码PCM数据
         * @param input pcm数据
         * @param presentationTimeUs 时间戳
         */
        private synchronized void encodData(byte[] input, long presentationTimeUs) {
            int inputBufferIndex = mEncoder.dequeueInputBuffer(-1);
            if (inputBufferIndex >= 0) {
                ByteBuffer inputBuffer = mEncodeInputBuffer[inputBufferIndex];
                inputBuffer.clear();
                inputBuffer.put(input);
                mEncoder.queueInputBuffer(inputBufferIndex, 0, input.length, presentationTimeUs, 0);
            }
    
            BufferInfo bufferInfo = new BufferInfo();
            int outputBufferIndex = mEncoder.dequeueOutputBuffer(bufferInfo, 0);
            while (outputBufferIndex >= 0) {
                int outBitsSize = bufferInfo.size;
                ByteBuffer outputBuffer = mEncodeOutputBuffer[outputBufferIndex];
                outputBuffer.position(bufferInfo.offset);
                outputBuffer.limit(bufferInfo.offset + outBitsSize);
                outputBuffer.position(bufferInfo.offset);
                mMediaMuxer.writeSampleData(mAudioTrackIndex, outputBuffer, bufferInfo);
                mEncoder.releaseOutputBuffer(outputBufferIndex, false);
                outputBufferIndex = mEncoder.dequeueOutputBuffer(bufferInfo, 0);
            }
        }
    
        /**
         * 输入要解码的buffer
         * @return 输入是否成功
         */
        private void decodeInputBuffer() {
            while (true) {
                int inputBufIndex = mDecoder.dequeueInputBuffer(TIMEOUT_US);
                if (inputBufIndex >= 0) {
                    mDecodeInputBuffer[inputBufIndex].clear();
                    long presentationTimeUs = mAudioExtractor.getSampleTime();
                    int sampleSize = mAudioExtractor.readSampleData(mDecodeInputBuffer[inputBufIndex], 0);
                    if (sampleSize > 0) {
                        if (presentationTimeUs > mMaxTimeStamp) {
                            return;//超过最大时间戳的音频不处理
                        }
                        mDecoder.queueInputBuffer(inputBufIndex, 0, sampleSize, presentationTimeUs, 0);
                        mAudioExtractor.advance();
                        decodeOutputBuffer();
                    }
                }
            }
        }
    
        /**
         * 初始化编码器
         * @return 初始化是否成功
         */
        private boolean initEncodeAudio() {
            try {
                //设置目标音频格式
                mEncoder = MediaCodec.createByCodecName("OMX.google.aac.encoder");
                MediaFormat mediaFormat = MediaFormat.createAudioFormat("audio/mp4a-latm", SAMPLE_RATE, 2);
                mediaFormat.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC);
                mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, BIT_RATE);
                mediaFormat.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, MAX_INPUT_SIZE);
                mEncoder.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
                mEncoder.start();
                mEncodeInputBuffer = mEncoder.getInputBuffers();
                mEncodeOutputBuffer = mEncoder.getOutputBuffers();
                return true;
            } catch (Exception e) {
                e.printStackTrace();
                Log.e(TAG, "initEncodeAudio---" + e.getMessage());
            }
            return false;
        }
    
        /**
         * 初始化音频解码器
         */
        private boolean initDecodeAudio() {
            try {
                mAudioFormat = MediaUtils.getMediaFormat(mAudioExtractor, MediaUtils.AUDIO_TYPE, mAudioPath);
                String mime = mAudioFormat.getString(MediaFormat.KEY_MIME);
                Log.e(TAG, "设置decoder的音频格式是:" + mime);
                mDecoder = MediaCodec.createDecoderByType(mime);
                mDecoder.configure(mAudioFormat, null, null, 0);
                mDecoder.start();
    
                mDecodeInputBuffer = mDecoder.getInputBuffers();
                mDecodeOutputBuffer = mDecoder.getOutputBuffers();
                return true;
            } catch (Exception e) {
                e.printStackTrace();
                Log.e(TAG, "initDecodeAudio---" + e.getMessage());
            }
            return false;
        }
    
        /**
         * 写入提取的数据到目标文件
         * @param mediaMuxer
         * @param trackIndex
         * @param extractor
         */
        private boolean handleTrack(MediaMuxer mediaMuxer, int trackIndex, MediaExtractor extractor) {
            if(mediaMuxer == null || trackIndex < 0 || extractor == null){
                return false;
            }
            ByteBuffer inputBuffer = ByteBuffer.allocate(VIDEO_READ_SAMPLE_SIZE);
            BufferInfo info = new BufferInfo();
            int sampleSize;
            while ((sampleSize = extractor.readSampleData(inputBuffer, 0)) > 0){
                info.offset = 0;
                info.size = sampleSize;
                info.flags = MediaCodec.BUFFER_FLAG_SYNC_FRAME;
                info.presentationTimeUs = extractor.getSampleTime();
                if(mMaxTimeStamp < info.presentationTimeUs){
                    break;
                }
                extractor.advance();
                mediaMuxer.writeSampleData(trackIndex, inputBuffer, info);
            }
            return true;
        }
    
        /**
         * 释放资源
         */
        private void release() {
            try {
                if(mEncoder != null) {
                    mEncoder.stop();
                    mEncoder.release();
                }
                if(mMediaMuxer != null) {
                    mMediaMuxer.stop();
                    mMediaMuxer.release();
                }
                if(mDecoder != null) {
                    mDecoder.stop();
                    mDecoder.release();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    代码看上去比较多,为了更好的理解,下面我将详细解释具体步骤,首先是初始化混合器:通过统一的createCompounder创建混合器,创建混合器除了一些常规的文件检查之外,还有一个很重要的是音乐类型的检查,因为MediaMuxer是不能直接写入mp3音乐格式的数据,但是MediaMuxer支持aac和m4a格式的音乐直接写入合成文件。同时因为MediaMuxer对mp4视频数据也支持直接写入,所以对于视频数据不需要重新编解码所以需要在创建混合器的时候检查一下音乐类型。

        /**
         *
         * @param videoPath 源视频路径
         * @param audioPath 音频路径
         * @param audioDEType 音频类型
         * @param dstPath 目标文件路径
         */
        private Compounder(String videoPath, String audioPath, int audioDEType, String dstPath){
            mVideoPath = videoPath;
            mAudioPath = audioPath;
            mAudioDEType = audioDEType;
            mDstFilePath = dstPath;
        }
    
        /**
         *
         * @param videoPath 源视频路径
         * @param audioPath 音频路径
         * @param dstPath 目标文件路径
         * @return null || compounder
         */
        public static Compounder createCompounder(String videoPath, String audioPath, String dstPath){
            if(checkVideo(videoPath)){
                int audioEDType = checkAudio(audioPath);
                if(audioEDType != NOT_SUPPORT_TYPE)
                    return new Compounder(videoPath, audioPath, audioEDType, dstPath);
            }
            return null;
        }

    创建好混合器Compounder之后,调用start函数就开始混合流程:

        /**
         * 开始合成
         */
        public void start(){
            if(mAudioDEType == MP3_TYPE) {
                initDecodeAudio();
                initEncodeAudio();
                if (initMuxer()) {
                    if (handleTrack(mMediaMuxer, mVideoTrackIndex, mVideoExtractor)) {
                        decodeInputBuffer();//解码--->编码--->合成输出文件
                    }
                }
            }else if (mAudioDEType == NOT_DC_EC_TYPE){//直接合成文件
                if(initMuxer()){
                    handleTrack(mMediaMuxer, mVideoTrackIndex, mVideoExtractor);
                    handleTrack(mMediaMuxer, mAudioTrackIndex, mAudioExtractor);
                }
            }
            release();
            Log.e(TAG, "finished~~~~~~~~~~~~~~~~~~~~");
        }

    为了更好的理解,下面是混合的流程图:
    这里写图片描述

    下面我将一一详述各个步骤。再次强调一遍哈!

    当音频是m4a或者aac格式的时候,混合器可以直接把音频数据写入文件。当音频数据是mp3格式的时候,需要将音频数据编解码成m4a或者aac格式,才能进行音视频数据混合

    先看音频为m4a和aac这条线:流程图如下
    这里写图片描述

    
        /**
         * 开始合成
         */
        public void start(){
            if(mAudioDEType == MP3_TYPE) {
                initDecodeAudio();
                initEncodeAudio();
                if (initMuxer()) {
                    if (handleTrack(mMediaMuxer, mVideoTrackIndex, mVideoExtractor)) {
                        decodeInputBuffer();//解码--->编码--->合成输出文件
                    }
                }
            }
    //===========m4a/aac主线流程代码====================
            else if (mAudioDEType == NOT_DC_EC_TYPE){//直接合成文件
                if(initMuxer()){
                    handleTrack(mMediaMuxer, mVideoTrackIndex, mVideoExtractor);
                    handleTrack(mMediaMuxer, mAudioTrackIndex, mAudioExtractor);
                }
            }
            release();
            Log.e(TAG, "finished~~~~~~~~~~~~~~~~~~~~");
        }
        //=====================================

    1,初始化混合器
    初始化混合器,首先要获取原视频文件的视频格式

    /**
    * 初始化混合器
    */
    private boolean initMuxer() {
       try {
           //获取视频格式
           mVideoFormat = MediaUtils.getMediaFormat(mVideoExtractor,
                   MediaUtils.VIDEO_TYPE, mVideoPath);
           if(mVideoFormat != null) {
               //设置mp4输出格式
               mMediaMuxer = new MediaMuxer(mDstFilePath,
                       MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
               //加入视频轨道,设置视频格式
               mVideoTrackIndex = mMediaMuxer.addTrack(mVideoFormat);
               mMaxTimeStamp = mVideoFormat.getLong(MediaFormat.KEY_DURATION);
               if(mAudioDEType == NOT_DC_EC_TYPE){
                   //获取音频格式
                   MediaFormat audioFormat = MediaUtils.getMediaFormat(mAudioExtractor,
                                                   MediaUtils.AUDIO_TYPE, mAudioPath);
                   if(audioFormat != null) {
                       mAudioTrackIndex = mMediaMuxer.addTrack(audioFormat);
                   }else{
                       return false;
                   }
               }
               else if(mAudioDEType == MP3_TYPE) {
                   if(mDecoder == null || mEncoder == null){
                       return false;
                   }
                   //加入音频轨道,设置目标的音频格式
                   mAudioTrackIndex = mMediaMuxer.addTrack(mEncoder.getOutputFormat());
               }
               mMediaMuxer.start();
               return true;
           }
       } catch (IOException e) {
           e.printStackTrace();
           mMediaMuxer = null;
       }
       return false;
    }

    这个步骤涉及到一个获取多媒体数据格式的函数,下面也贴出对应的代码:

     /**
      * 获取媒体编码格式
      * @param extractor
      * @param type
      * @param mediaPath
      * @return
      */
     public static MediaFormat getMediaFormat(MediaExtractor extractor, String type, String mediaPath){
         if(extractor == null || mediaPath == null || mediaPath.equals("")){
             return null;
         }
         try {
             MediaFormat format;
             extractor.setDataSource(mediaPath);
             for(int i = 0; i < extractor.getTrackCount(); i++){
                 format = extractor.getTrackFormat(i);
                 if(format.getString(MediaFormat.KEY_MIME).startsWith(type)){
                     extractor.selectTrack(i);
                     return format;
                 }
             }
         }catch (IOException e){
             Log.e(TAG, e.getMessage());
         }
         return null;
     }

    通过MediaMuxer.addTrack来标明音频轨道和视频数据轨道。这个是非常重要的,因为只有加入了音视频对应的数据轨道,后面才能把对应的数据写入对应的轨道。

    2,写入视频数据 && 3,写入音频数据
    通过MediaExtractor提取MP4数据,因为MediaMuxer可以直接合成mp4数据,所以此处不用对mp4数据重新编解码,同样的MediaMuxer也支持直接合成m4a和aac数据,所以也不用重新编解码音频数据。此处为了避免代码重复,写了一个通用的handleTrack函数来写入指定的音视频数据,直接通过MediaExtractor提取数据,然后通过MediaMuxer写入数据。

      /**
       * 开始合成
       */
      public void start(){
          if(mAudioDEType == MP3_TYPE) {
              initDecodeAudio();
              initEncodeAudio();
              if (initMuxer()) {
                  //写入视频数据
                  if (handleTrack(mMediaMuxer, mVideoTrackIndex, mVideoExtractor)) {
                      decodeInputBuffer();//mp3音频:解码--->编码--->合成输出文件
                  }
              }
          }else if (mAudioDEType == NOT_DC_EC_TYPE){//音频为m4a/aac直接合成文件
              if(initMuxer()){
                  //写入视频数据
                  handleTrack(mMediaMuxer, mVideoTrackIndex, mVideoExtractor);
                  //写入音频数据
                  handleTrack(mMediaMuxer, mAudioTrackIndex, mAudioExtractor);
              }
          }
          release();
          Log.e(TAG, "finished~~~~~~~~~~~~~~~~~~~~");
      }
    /**
     * 写入提取的数据到目标文件
     * @param mediaMuxer 数据合成器
     * @param trackIndex  数据轨道
     * @param extractor   数据提取器
     */
    private boolean handleTrack(MediaMuxer mediaMuxer, int trackIndex, MediaExtractor extractor) {
        if(mediaMuxer == null || trackIndex < 0 || extractor == null){
            return false;
        }
        ByteBuffer inputBuffer = ByteBuffer.allocate(VIDEO_READ_SAMPLE_SIZE);
        BufferInfo info = new BufferInfo();
        int sampleSize;
        while ((sampleSize = extractor.readSampleData(inputBuffer, 0)) > 0){
            info.offset = 0;
            info.size = sampleSize;
            info.flags = MediaCodec.BUFFER_FLAG_SYNC_FRAME;
            info.presentationTimeUs = extractor.getSampleTime();
            if(mMaxTimeStamp < info.presentationTimeUs){
                break;
            }
            extractor.advance();
            mediaMuxer.writeSampleData(trackIndex, inputBuffer, info);
        }
        return true;
    }

    4,释放资源,合成结束
    合成完成之后,务必要释放资源,否则容易引起内存泄漏,以及一系列崩溃

        /**
         * 释放资源
         */
        private void release() {
            try {
                if(mEncoder != null) {
                    mEncoder.stop();
                    mEncoder.release();
                }
                if(mMediaMuxer != null) {
                    mMediaMuxer.stop();
                    mMediaMuxer.release();
                }
                if(mDecoder != null) {
                    mDecoder.stop();
                    mDecoder.release();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

    说完m4a/aac这条线,接下来说的是mp3这条线:
    MediaMuxer不支持直接合成mp3音频数据,所以需要先对mp3数据进行解码成pcm数据,然后编码成m4a或者aac数据,才能够写入合成文件。

    
        /**
         * 开始合成
         */
        public void start(){
        //========================Mp3主线代码流程======================
            if(mAudioDEType == MP3_TYPE) {
                initDecodeAudio();
                initEncodeAudio();
                if (initMuxer()) {
                    if (handleTrack(mMediaMuxer, mVideoTrackIndex, mVideoExtractor)) {
                        decodeInputBuffer();//解码--->编码--->合成输出文件
                    }
                }
            }
    //=========================================================
            else if (mAudioDEType == NOT_DC_EC_TYPE){//直接合成文件
                if(initMuxer()){
                    handleTrack(mMediaMuxer, mVideoTrackIndex, mVideoExtractor);
                    handleTrack(mMediaMuxer, mAudioTrackIndex, mAudioExtractor);
                }
            }
            release();
            Log.e(TAG, "finished~~~~~~~~~~~~~~~~~~~~");
        }

    流程图如下:
    这里写图片描述

    1,初始化音频解码器
    通过MediaExtractor获取音频数据格式,并用它来初始化解码器,初始化解码器后,需要获取对应的输入数据缓存和输出数据缓存

        /**
         * 初始化音频解码器
         */
        private boolean initDecodeAudio() {
            try {
                mAudioFormat = MediaUtils.getMediaFormat(mAudioExtractor, MediaUtils.AUDIO_TYPE, mAudioPath);
                String mime = mAudioFormat.getString(MediaFormat.KEY_MIME);
                Log.e(TAG, "设置decoder的音频格式是:" + mime);
                mDecoder = MediaCodec.createDecoderByType(mime);
                mDecoder.configure(mAudioFormat, null, null, 0);
                mDecoder.start();//记得需要start
                //解码器输入数据缓存
                mDecodeInputBuffer = mDecoder.getInputBuffers();
                //解码器输出数据缓存
                mDecodeOutputBuffer = mDecoder.getOutputBuffers();
                return true;
            } catch (Exception e) {
                e.printStackTrace();
                Log.e(TAG, "initDecodeAudio---" + e.getMessage());
            }
            return false;
        }

    2,初始化音频解码器
    指定一个m4a或者aac编码器,代码中使用的是OMX.google.aac.encoder,这个编码器绝大部分android手机都有,是google自带的编码器,所以属于软编码,但是音频数据编码速度很快,所以不用太在意软硬编码的问题(想编码视频数据朋友就得注意了,硬编码才是第一选择),同样的,初始化完后需要获取对应的编码器输入数据缓存和编码器输出数据缓存。

        /**
         * 初始化编码器
         * @return 初始化是否成功
         */
        private boolean initEncodeAudio() {
            try {
                //设置目标音频格式
                mEncoder = MediaCodec.createByCodecName("OMX.google.aac.encoder");
                MediaFormat mediaFormat = MediaFormat.createAudioFormat("audio/mp4a-latm", SAMPLE_RATE, 2);
                mediaFormat.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC);
                mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, BIT_RATE);
                mediaFormat.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, MAX_INPUT_SIZE);
                mEncoder.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
                mEncoder.start();//记得需要start
                //编码器数据输入缓存
                mEncodeInputBuffer = mEncoder.getInputBuffers();
                //编码器数据输出缓存
                mEncodeOutputBuffer = mEncoder.getOutputBuffers();
                return true;
            } catch (Exception e) {
                e.printStackTrace();
                Log.e(TAG, "initEncodeAudio---" + e.getMessage());
            }
            return false;
        }

    3,初始化混合器
    和m4a/aac主线一样,请参考上面描述

    4,写入视频数据
    和m4a/aac主线一样,请参考上面描述

    5,解码mp3数据为pcm && 6,编码mp3数据为m4a&&7,写入音频数据
    解码–编码–写入音频数据,三个步骤是线性同步的,因为不可能把数据都缓存在内从中,那样音频稍微大一点,内存就爆了。解码完一定的数据,就要把解码好的数据输出,然后进行编码,接着把编码好的数据进行输出,最后把编码好的m4a数据写入mp4文件对应的音频轨道。下面是解码-编码-写入音频数据的流程图:
    这里写图片描述

    下面是具体的代码流程:

     /**
      * 输入要解码的buffer
      * @return 输入是否成功
      */
     private void decodeInputBuffer() {
         while (true) {
             int inputBufIndex = mDecoder.dequeueInputBuffer(TIMEOUT_US);
             if (inputBufIndex >= 0) {
                 mDecodeInputBuffer[inputBufIndex].clear();
                 long presentationTimeUs = mAudioExtractor.getSampleTime();
                  //读取数据到解码器输入缓存
                 int sampleSize = 
                 mAudioExtractor.readSampleData(mDecodeInputBuffer[inputBufIndex], 0);
                 if (sampleSize > 0) {
                     if (presentationTimeUs > mMaxTimeStamp) {
                         return;//超过最大时间戳的音频不处理
                     }
                     //将对数据进行解码
                     mDecoder.queueInputBuffer(inputBufIndex, 0, sampleSize, 
                                              presentationTimeUs, 0);
                     mAudioExtractor.advance();
                     //输出解码后数据,然后进行编码输出------important
                     decodeOutputBuffer();
                 }
             }
         }
     }
    
        /**
         * 获取输出的解码后的buffer
         */
        private void decodeOutputBuffer() {
            BufferInfo info = new BufferInfo();
            //输出解码的pcm数据
            int outputIndex = mDecoder.dequeueOutputBuffer(info, -1);
            if (outputIndex >= 0) {
                byte[] chunk = new byte[info.size];
                mDecodeOutputBuffer[outputIndex].position(info.offset);
                mDecodeOutputBuffer[outputIndex].limit(info.offset + info.size);
                //将解码数据转为byte
                mDecodeOutputBuffer[outputIndex].get(chunk);
                mDecodeOutputBuffer[outputIndex].clear();
                //必须要释放解码器缓存
                mDecoder.releaseOutputBuffer(outputIndex, false);
                if (chunk.length > 0) {
                //将pcm数据提供给编码器进行编码
                    encodData(chunk, info.presentationTimeUs);
                }
            }
        }
    
        /**
         * 编码PCM数据
         * @param input pcm数据
         * @param presentationTimeUs 时间戳
         */
        private synchronized void encodData(byte[] input, long presentationTimeUs) {
            int inputBufferIndex = mEncoder.dequeueInputBuffer(-1);
            if (inputBufferIndex >= 0) {
                ByteBuffer inputBuffer = mEncodeInputBuffer[inputBufferIndex];
                inputBuffer.clear();
                inputBuffer.put(input);
                //将pcm数据输入编码器缓存
                mEncoder.queueInputBuffer(inputBufferIndex, 0, input.length, presentationTimeUs, 0);
            }
    
            BufferInfo bufferInfo = new BufferInfo();
            //编码器中输出m4a数据
            int outputBufferIndex = mEncoder.dequeueOutputBuffer(bufferInfo, 0);
            while (outputBufferIndex >= 0) {
                int outBitsSize = bufferInfo.size;
                ByteBuffer outputBuffer = mEncodeOutputBuffer[outputBufferIndex];
                outputBuffer.position(bufferInfo.offset);
                outputBuffer.limit(bufferInfo.offset + outBitsSize);
                outputBuffer.position(bufferInfo.offset);
                //将编码器输出缓存写入到mp4文件---写入音频数据
                mMediaMuxer.writeSampleData(mAudioTrackIndex, outputBuffer, bufferInfo);
                mEncoder.releaseOutputBuffer(outputBufferIndex, false);
                outputBufferIndex = mEncoder.dequeueOutputBuffer(bufferInfo, 0);
            }
        }

    上面就是一个很完整的解码–编码流程,请各位朋友仔细看代码注释,这些地方都是关键步骤,通过这些步骤能够很清晰的明白代码逻辑。

    8,结束合成,释放资源
    释放资源时候,需要释放所有的编解码器,以及混合器。

        /**
         * 释放资源
         */
        private void release() {
            try {
                if(mEncoder != null) {
                    mEncoder.stop();
                    mEncoder.release();
                }
                if(mMediaMuxer != null) {
                    mMediaMuxer.stop();
                    mMediaMuxer.release();
                }
                if(mDecoder != null) {
                    mDecoder.stop();
                    mDecoder.release();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

    好了,上面就是音视频混合的所有流程,有疑问的朋友欢迎一起沟通交流,共同进步,发现代码有问题的朋友,也非常欢迎指出来,大家一起学习。

    展开全文
  • 概述 通常来说,对于同一平台同一...在Android中,我们可以直接使用MediaRecord来进行录像,但是在很多适合MediaRecord并不能满足我们的需求,比如我们需要对录制的视频加水印或者其他处理后,所有的平台都按照同一
  • mp4v2和faac混合为mp4, 在Hi3519板运行成功。生成的joseph_mp4_test运行时需要av_file中的资源(this is Joseph mux sample)
  • Android硬编码——音频编码、视频编码及音视频混合

    万次阅读 多人点赞 2017-01-04 15:22:54
    视频编解码对许多Android程序员来说都是Android中比较难的一个知识点。在Android 4.1以前,Android并没有提供硬编硬解的API,所以之前基本上都是采用FFMpeg来做视频软件编解码的,现在FFMpeg在Android的编解码上依旧...
  • ffmpeg混音以及音视频混合处理

    千次阅读 2019-01-14 14:37:16
    ffmpeg混音以及音视频混合处理 ffmpeg的命令 这里是我最近研究的音视频混合处理的一些ffmpeg的命令 // 音频拼接 ffmpeg - i "concat:first.mp3|second.mp3" - acodec copy third . mp3 ( third格式和...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 39,310
精华内容 15,724
关键字:

音视频混合