精华内容
下载资源
问答
  • 本文将介绍如何用Pico示波器做音频频谱分析
  • 怎样做到音频合并无缝衔接

    千次阅读 2019-07-11 18:44:52
    怎样做到无缝衔接?其实只要时间对上,音频转呈幅度不要太大就没问题,主要还是音频合并,想要做要音频... 首先要打开我们的迅捷音频转换器,然后点击“音频合并”选项,会看到我们做音频合并的界面。 2、第二步 ...

          怎样做到无缝衔接?其实只要时间对上,音频转呈幅度不要太大就没问题,主要还是音频合并,想要做要音频合并,肯定要有一个好一点的合并软件,在合并结果质量的前提上还要更加快捷和方便,有利于新手操作,那小编就来推荐一个较好的软件,怎么操作下面有跟大家说。

    1、第一步
          首先要打开我们的迅捷音频转换器,然后点击“音频合并”选项,会看到我们做音频合并的界面。

    2、第二步
          你可以直接拖入想要剪切的文件进来,当然也可以点击“添加文件”按钮来添加单个的文件,亦或者点击“添加文件夹”来添加文件夹中的多个文件来进行操作,把你想要合并的音乐都放在列表里。

    3、第三步
           点击添加文件或者添加文件夹,当然也可以直接拖入音频文件,然后进行你想要合并或者先分割再合并,有几种分割方法,看你喜欢了,点击“开始合并”选项就可以开始合并了。

    4、第四步
           等待合并进度条满时,就可以完成文件合并了,时间可能会比较长,请大家耐心等待。

    5、第五步
           点击“打开文件夹”按钮,就能将文件保存并且可以打开文件所在位置对文件进行查看了。

           大家所关注到的点,小编应该都说了,音频如何无缝衔接合并,就说完了,大家还是要去操作一下,理论百遍,不如动手实践,愿你成功。

    展开全文
  • 怎样提取音频制作手机铃声

    千次阅读 2019-07-09 11:31:02
    如果你在看一部电影,发现里面的背景音乐配音非常好听,想要下载把它放到手机上铃声之类的,可是却找不到歌名叫什么?有什么办法找到呢?其实不用费心思去找,这里教给大家一个方法,可以利用迅捷视频转换器把它...

    如果你在看一部电影,发现里面的背景音乐配音非常好听,想要下载把它放到手机上做铃声之类的,可是却找不到歌名叫什么?有什么办法找到呢?其实不用费心思去找,这里教给大家一个方法,可以利用迅捷视频转换器把它转换成想要的音乐格式。具体的方法是先利用一款音频转换器把那段音乐视频文件截取下来,然后再转换成想要的音频格式。

    方法步骤:

    1、打开可以看到,迅捷音频转换器的功能是比较强大的。点击“添加文件”按钮,把要提取背景音乐的电影文件给加载进来。支持单个文件添加,同时也支持批量文件添加的。

    2、如果你是想要截取电影中的某一段的,那么你就要点击“提取音频”按钮,之后进到(如下图)界面,电影会在此界面中自动播放,选好需要截取片段的起始时间,选好后点击“确定”按钮即可。

    3、下面就是来选好你要转换的音频格式。在“输出格式”上面可以选择输出音频的格式,在“音频转换”栏目中选择要转换的音频格式“MP3”。

    4、最后点击右下角的“开始转换”按钮即可开始转换啦。如果想要继续转换的,点击下面“取消”按钮,即可停止转换。等到转换完成之后,点击后面的“打开”按钮就可以到达输出文件的保存位置了。

    5、在上面的操作过程中,都是比较简单化。转换完成的MP3格式音频我们就可以通过数据线传输到手机中,设置为手机铃声了。

    此音频转换器还有其他很多的功能哦,比如音频合并、音频剪切等等。大家有兴趣可以一一尝试噢!

    展开全文
  • 前言  这篇博客,主要讲解的是android端的音频处理,在开发Android视频编辑器的时候,有一个非常重要的点就是音频的相关处理...那这些功能的运用场景哪里呢?比如如果我们想给视频文件增加要给bgm,那如果保留原...

    前言

         这篇博客,主要讲解的是android端的音频处理,在开发Android视频编辑器的时候,有一个非常重要的点就是音频的相关处理。比如如何从视频中分离音频(保存为mp3文件),然后分离出来的音频如何单声道和双声道互转,还有就是如果把两个音频文件合并为一个音频文件(音频混音),以及如何调节音频的原始大小。那这些功能的运用场景做哪里呢?比如如果我们想给视频文件增加要给bgm,那如果保留原声的情况下,就需要用到音频的混音,将原声和bgm合并为一个音频文件,然后两种声音的相对大小我们肯定也是需要可以调节的,比如是bgm声音大一点还是原声音量大一点。这里还会存在另外一个问题,就是双声道和单声道的音频不能直接混音,所以我们也需要学习双声道和单声道互转。其实类似的功能软件做电脑端已经有很多,而我们这里就是探究他的实现原理,从而开发出android端的音频处理功能。下面我们就一一来实现这些功能。

        这篇博客的重点内容包括android平台的音频编解码、音频的一些基础知识、归一化混音算法等。

        本系列的文章包括如下:

           1、android视频编辑器之视频录制、断点续录、对焦等

           2、android视频编辑器之录制过程中加水印和美白效果

           3、android视频编辑器之本地视频加美白效果和加视频水印

           4、android视频编辑器之通过OpenGL给视频增加不同的滤镜效果

           5、android视频编辑器之音频编解码、从视频中分离音频、音频混音、音频音量调节等

           6、android视频编辑器之通过OpenGL做不同视频的拼接

           7、android视频编辑器之音视频裁剪、增加背景音乐等

     

    从视频文件中分离出音频文件

        首先,我们来实现我们的第一个功能,从视频文件中把音频文件分离出来,我们的目标是从一个完整的视频中,分离出他的音频并且保存为aac文件或者mp3文件。首先在android音视频的处理相关类中,我们需要有两个很重要的类,一个是音视频的分离器MediaExtractor 另一个就是音视频的混合器MediaMuxer。

        

         MediaExtractor:可以从当前的视频文件中读取到音视频相关的信息(音视频的编码格式等),并且逐帧读取文件中的音视频数据。

         MediaMuxer:可以将编码后的音视频数据保存为一个独立的文件,可以只写入音频数据保存为音频文件,也可以同时写入音频和视频数据,保存为一个有画面有声音的视频文件。

     

        要实现我们的目标,那我们已经很清楚的知道一个大致的过程了,那就是从MediaExtractor从视频中分离出音频数据(编码好的),通过MediaMuxer,将分离出来的音频数据保存为一个音频文件。

     

        首先需要初始化一个分离器MediaExtractor,并且给他设置文件,拿到音频的format和信道

    MediaExtractor extractor = new MediaExtractor();
    int audioTrack = -1;
    boolean hasAudio = false;
    try {
         extractor.setDataSource(videoPath);
         for (int i = 0; i < extractor.getTrackCount(); i++) {
              MediaFormat trackFormat = extractor.getTrackFormat(i);
              String mime = trackFormat.getString(MediaFormat.KEY_MIME);
              if (mime.startsWith("audio/")) {
                    audioTrack = i;
                    hasAudio = true;
                    break;
              }
          }

         上段代码的意思,就是初始化了一个音视频分离器,然后通过setDataSource给他设置了数据源,然后遍历该数据源的所有信道,如果他KEY_MIME是“audio/”开头的,说明他是音频的信道,也就是我们所需要的音频数据所在的信道。然后我们记录下audioTrack。然后我们需要选中音频信道

      if (hasAudio) {
             extractor.selectTrack(audioTrack);
             ...
      }

        接下来,我们就需要初始化一个混合器MediaMuxer了  

         MediaMuxer mediaMuxer = new MediaMuxer(audioSavePath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
         MediaFormat trackFormat = extractor.getTrackFormat(audioTrack);
         int writeAudioIndex = mediaMuxer.addTrack(trackFormat);
         mediaMuxer.start();
         ByteBuffer byteBuffer = ByteBuffer.allocate(trackFormat.getInteger(MediaFormat.KEY_MAX_INPUT_SIZE));
         MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();

        上面的代码中,我们就初始化了一个MediaMuxer,并且设置了输出文件的位置和输出的格式,并且初始化了一个buffer缓冲区和一个用来保存视频信息的BufferInfo,然后我们可以开始读取和写入数据了

     extractor.readSampleData(byteBuffer, 0);
     if (extractor.getSampleFlags() == MediaExtractor.SAMPLE_FLAG_SYNC) {
              extractor.advance();
     }
     while (true) {
              int readSampleSize = extractor.readSampleData(byteBuffer, 0);
              Log.e("hero","---读取音频数据,当前读取到的大小-----:::"+readSampleSize);
              if (readSampleSize < 0) {
                        break;
               }
    
               bufferInfo.size = readSampleSize;
               bufferInfo.flags = extractor.getSampleFlags();
               bufferInfo.offset = 0;
               bufferInfo.presentationTimeUs = extractor.getSampleTime();
               Log.e("hero","----写入音频数据---当前的时间戳:::"+extractor.getSampleTime());
    
               mediaMuxer.writeSampleData(writeAudioIndex, byteBuffer, bufferInfo);
               extractor.advance();//移动到下一帧
      }
      mediaMuxer.release();
      extractor.release();


        上面的代码,简单的说就是通过readSampleData从分离器中读取数据,如果没有读到说明分离完成了,就break掉,如果有数据,就设置当前帧的bufferInfo,比如偏移量,标志,时间戳,当前帧大小等,然后把当前帧数据和帧信息写入到混合器MediaMuxer中,然后开始读取下一帧。

        这样通过一个死循环,就可以读取到媒体文件中我们指定信道的所有数据,并且通过混合器保存为一个单独的文件了,这样就可以在android平台将视频中的音视频进行分离而且不需要编解码。

    音频文件转PCM数据

        在上面我们已经从视频中分离出了音频文件,接下来,我们要将音频文件进行解码,还原成原始的PCM数据。将音频进行解码的方式有很多,但是大多都是c/c++的方式,比如ffmpeg等,不过,在android中,4.0之后的版本已经支持音视频的硬编码了,所以我们这里用MediaCodec对音频进行解码,保存成PCM文件。MediaCodec是android平台对音视频进行编解码的非常重要的一个类。我们的基本流程是从MediaExtacror里面里面读取音频信道的数据,给到音频解码器进行解码,然后将解码出来的数据写到文件里面。

        所谓的PCM文件,其实就是音频在系统中保存的原始音频数据,没有经过编码的,而我们常见的mp3,aac等是经过编码的音频数据。

        我们要解码音频,首先需要一个分离器,MediaExtactor,哈哈是不是很眼熟,是的,我们刚才才使用了这个类,他不仅仅能读取视频文件,也可以读取单个音频文件,而且用法和上面分离音频差别不大,那么首先我们初始化一个,并且给他设置数据源。

         MediaExtractor extractor = new MediaExtractor();
         int audioTrack = -1;
         boolean hasAudio = false;
         try {
             extractor.setDataSource(audioPath);
             for (int i = 0; i < extractor.getTrackCount(); i++) {
                 MediaFormat trackFormat = extractor.getTrackFormat(i);
                 String mime = trackFormat.getString(MediaFormat.KEY_MIME);
                 if (mime.startsWith("audio/")) {
                        audioTrack = i;
                        hasAudio = true;
                        break;
                  }
              }
              if (hasAudio) {
                  extractor.selectTrack(audioTrack);
                   ...
              }

        同样,因为我们是要解码音频数据,所以就拿到音频的信道。


        然后,接下来就是音频解码中非常重要的一些知识了,首先初始化音频的解码器

       MediaFormat trackFormat = extractor.getTrackFormat(audioTrack);
                    //初始化音频的解码器
       MediaCodec audioCodec = MediaCodec.createDecoderByType(trackFormat.getString(MediaFormat.KEY_MIME));
       audioCodec.configure(trackFormat, null, null, 0);
    
       audioCodec.start();
    
       ByteBuffer[] inputBuffers = audioCodec.getInputBuffers();
       ByteBuffer[] outputBuffers = audioCodec.getOutputBuffers();
       MediaCodec.BufferInfo decodeBufferInfo = new MediaCodec.BufferInfo();
       MediaCodec.BufferInfo inputInfo = new MediaCodec.BufferInfo();

        上面的代码,就是从分离器中拿到音频信道的MediaFormat,然后通过MediaCodec.createDecoderbyType,初始化一个相应音频格式的解码器,MediaFormat.KEY_MIME的值,其实在MediaFormat中已经列举出来了。

    <Image_1>

     

         然后把要解码音频数据的mediaFormat通过audioCodec.configure方法进行设置,再通过start方法开启解码器,我们提前拿到了输入的inputBuffer和输出的outputBuffer。

         然后,我们整体的解码过程就是,首先遍历解码器的所有inputBuffers,如果是可以使用的,就从分离器中读取数据,并且给到inputBuffers。

        for (int i = 0; i < inputBuffers.length; i++) {
                 //遍历所以的编码器 然后将数据传入之后 再去输出端取数据
                 int inputIndex = audioCodec.dequeueInputBuffer(TIMEOUT_USEC);
                 if (inputIndex >= 0) {
                          /**从分离器中拿到数据 写入解码器 */
                          ByteBuffer inputBuffer = inputBuffers[inputIndex];//拿到inputBuffer
                          inputBuffer.clear();//清空之前传入inputBuffer内的数据
                          int sampleSize = extractor.readSampleData(inputBuffer, 0);//MediaExtractor读取数据到inputBuffer中
    
                          if (sampleSize < 0) {
                                  audioCodec.queueInputBuffer(inputIndex, 0, 0, 0L, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
                                  inputDone = true;
                           } else {
    
                                  inputInfo.offset = 0;
                                  inputInfo.size = sampleSize;
                                  inputInfo.flags = MediaCodec.BUFFER_FLAG_SYNC_FRAME;
                                  inputInfo.presentationTimeUs = extractor.getSampleTime();
    
                                   audioCodec.queueInputBuffer(inputIndex, inputInfo.offset, sampleSize, inputInfo.presentationTimeUs, 0);//通知MediaDecode解码刚刚传入的数据
                                    extractor.advance();//MediaExtractor移动到下一取样处
                            }
                     }
         }

         如果dequeueInputbuffer拿到的inputIndex = -1,说明这个输入流不可用,如果可以用,我们就从输入流的数组中取到相应位置的输入流,然后清空之前遗留的数据,通过分离器的readSampleData读取音频数据,如果读到的数据大小小于0,说明数据已经读取完毕了,就将对应的输入流插入一个流已经结束的标志位
         MediaCodec.BUFFER_FLAG_END_OF_STREAM。大于0的话,就初始化输入流的InputInfo,包括当前数据的时间戳,偏移量等等,然后通知解码器对数据进行解码audioCodec.queueInputBuffer。并且通过extactor.advance()将分离器移动到下一个取数据的地方。
          现在,我们已经往编码器中写入了很多数据,需要从解码器的输出流中读取解码后的数据了。

        while (!decodeOutputDone) {
              int outputIndex = audioCodec.dequeueOutputBuffer(decodeBufferInfo, TIMEOUT_USEC);
              if (outputIndex == MediaCodec.INFO_TRY_AGAIN_LATER) {
                        /**没有可用的解码器output*/
                        decodeOutputDone = true;
               } else if (outputIndex == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
                        outputBuffers = audioCodec.getOutputBuffers();
               } else if (outputIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
                        MediaFormat newFormat = audioCodec.getOutputFormat();
               } else if (outputIndex < 0) {
               } else {
                        ByteBuffer outputBuffer;
                        if (Build.VERSION.SDK_INT >= 21) {
                                outputBuffer = audioCodec.getOutputBuffer(outputIndex);
                        } else {
                                outputBuffer = outputBuffers[outputIndex];
                        }
    
                        chunkPCM = new byte[decodeBufferInfo.size];
                        outputBuffer.get(chunkPCM);
                        outputBuffer.clear();
    
                        fos.write(chunkPCM);//数据写入文件中
                        fos.flush();
                 }
                 audioCodec.releaseOutputBuffer(outputIndex, false);
                 if ((decodeBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
                         /**
                          * 解码结束,释放分离器和解码器
                          * */
                          extractor.release();
    
                          audioCodec.stop();
                          audioCodec.release();
                          codeOver = true;
                          decodeOutputDone = true;
                   }
            }

            下面,我们来大致解释一下上面的这些代码,通过dequeueOutputBuffer遍历,如果当前有可以使用的输出流,就通过outputIndex拿到对应位置的OutputBuffer,并且从该输出流缓冲区,读取到byte数据,这个byte数据就是音频解码出来的最原始的音频数据,其实就是一个byte数组。现实世界的声音转化成电脑或者手机系统里面的数据的时候,其实大致过程就是通过取样以及离散化,对脉冲信号进行编码调制,最终转化成PCM格式的数据,也就是一个byte的数组,这就是所有mp3或者aac等音频格式解码出来的在系统中的最原始数据,所谓的PCM格式的音频数据其实可以简单的理解为一个byte数组,而为什么又会有mp3或者是aac的区别呢,因为原始的PCM数据的数据量是非常大的,为了便于保存和传输,就制定了许多不同的编码格式,对一些杂音或者是不重要的数据进行分离,常见的就有mp3、AAC、wav等等,而这样的编码或多或少都是有损的编码,而音质越好,他的数据量就越大,比如wav这种无损的编码格式,同样一首歌,wav格式的大小就远远大于了mp3或者是AAC。

           言归正传,在我们读取到界面后的byte数据之后,我们就可以通过io,写入到一个本地文件中,从输出流中读取到数据后,一定记得释放到当前的输出缓冲区 audioCodec.releaseOutputBuffer(outputIndex, false);

     

            当然如果我们已经读取到了数据的末尾,就说明整个音频已经解码完毕了,我们就释放掉分离器MediaExtactor和解码器MediaCodec。

           

           通过上面两个 两个步骤,我们就完成了读取一个音频文件里面的数据,并且将数据进行解码保存成原始的PCM文件了,是不是非常简单呢?当然我们可以将上面两部分进行结合,就可以直接从一个视频文件中读取音频数据并且进行解码了。

     

           既然解码如此简单,那么我们怎么样才能将解码出来的PCM数据还原为一个可以播放的音频文件呢?下面这个我们就将介绍如何才能达成我们的目标。


    PCM数据转音频文件

          在上面的两部分内容中我们分别实现了从一个视频文件中将音频给分离出来,以及将一个音频文件解码成最原始的PCM数据。那么现在我们来将PCM文件进行编码成一个可以正常播放的音频文件。基本思路是通过io流从PCM文件中读取数据,然后将数据送到编码器中,进行编码,然后将编码完成的数据,通过io流写到一个文件中即可

          首先,我们需要初始化一个文件读取流,从PCM文件中读取数据

        FileInputStream fis = new FileInputStream(pcmPath);

          然后,初始化编码器相关的东西

       int inputIndex;
       ByteBuffer inputBuffer;
       int outputIndex;
       ByteBuffer outputBuffer;
       byte[] chunkAudio;
       int outBitSize;
       int outPacketSize;
       //初始化编码器
       MediaFormat encodeFormat = MediaFormat.createAudioFormat("audio/mp4a-latm", 44100, 2);//mime type 采样率 声道数
       encodeFormat.setInteger(MediaFormat.KEY_BIT_RATE, 96000);//比特率
       encodeFormat.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC);
       encodeFormat.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, 500 * 1024);
    
       MediaCodec mediaEncode = MediaCodec.createEncoderByType("audio/mp4a-latm");
       mediaEncode.configure(encodeFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
       mediaEncode.start();
    
       ByteBuffer[] encodeInputBuffers = mediaEncode.getInputBuffers();
       ByteBuffer[] encodeOutputBuffers = mediaEncode.getOutputBuffers();
       MediaCodec.BufferInfo encodeBufferInfo = new MediaCodec.BufferInfo();

          上面的代码可以看到,我们这里的输出音频格式和采样率,比特率等等都是写死的,但是原始的音频并不一定就是同样的配置,所以可能会存在问题,比如你解码的音频是mp3格式,而我们这里保存的是aac格式的文件,很明显的一个差别就是aac文件的大小会比原始的mp3格式的音频更小。

          接下来,我们初始化一个文件写入流,可以让我们把编码出来的音频数据写入到一个aac文件中。

      FileOutputStream fos = new FileOutputStream(new File(audioPath));
      BufferedOutputStream bos = new BufferedOutputStream(fos, 500 * 1024);

         肯定,下面就是最核心的读取数据—>给到编码器—>编码器的输出流拿数据—>通过io写入到文件中,这样一个循环中

           boolean isReadEnd = false;
           while (!isReadEnd) {
                for (int i = 0; i < encodeInputBuffers.length - 1; i++) {
                      if (fis.read(buffer) != -1) {
                            allAudioBytes = Arrays.copyOf(buffer, buffer.length);
                       } else {
                            Log.e("hero", "---文件读取完成---");
                            isReadEnd = true;
                            break;
                        }
                        Log.e("hero", "---io---读取文件-写入编码器--" + allAudioBytes.length);
                        inputIndex = mediaEncode.dequeueInputBuffer(-1);
                        inputBuffer = encodeInputBuffers[inputIndex];
                        inputBuffer.clear();//同解码器
                        inputBuffer.limit(allAudioBytes.length);
                        inputBuffer.put(allAudioBytes);//PCM数据填充给inputBuffer
                        mediaEncode.queueInputBuffer(inputIndex, 0, allAudioBytes.length, 0, 0);//通知编码器 编码
                    }
                    outputIndex = mediaEncode.dequeueOutputBuffer(encodeBufferInfo, 10000);//同解码器
                    while (outputIndex >= 0) {
                        //从编码器中取出数据
                        outBitSize = encodeBufferInfo.size;
                        outPacketSize = outBitSize + 7;//7为ADTS头部的大小
                        outputBuffer = encodeOutputBuffers[outputIndex];//拿到输出Buffer
                        outputBuffer.position(encodeBufferInfo.offset);
                        outputBuffer.limit(encodeBufferInfo.offset + outBitSize);
                        chunkAudio = new byte[outPacketSize];
                        AudioCodec.addADTStoPacket(chunkAudio, outPacketSize);//添加ADTS 代码后面会贴上
                        outputBuffer.get(chunkAudio, 7, outBitSize);//将编码得到的AAC数据 取出到byte[]中 偏移量offset=7 你懂得
                        outputBuffer.position(encodeBufferInfo.offset);
                        Log.e("hero", "--编码成功-写入文件----" + chunkAudio.length);
                        bos.write(chunkAudio, 0, chunkAudio.length);//BufferOutputStream 将文件保存到内存卡中 *.aac
                        bos.flush();
    
                        mediaEncode.releaseOutputBuffer(outputIndex, false);
                        outputIndex = mediaEncode.dequeueOutputBuffer(encodeBufferInfo, 10000);
                    }
                }
                mediaEncode.stop();
                mediaEncode.release();
                fos.close();


          上面的代码还是比较容易懂的,但是有个值得注意的点就是,首先我们每次从文件中读取的byte的大小是有限制的,你不能一下子读太多数据,如果一下子给太多数据编码器会卡死,我们这里限制的是8 * 1024 ,然后有一个就是addADTStopacket方法。

       /**
         * 写入ADTS头部数据
         * */
        public static void addADTStoPacket(byte[] packet, int packetLen) {
            int profile = 2; // AAC LC
            int freqIdx = 4; // 44.1KHz
            int chanCfg = 2; // CPE
    
            packet[0] = (byte) 0xFF;
            packet[1] = (byte) 0xF9;
            packet[2] = (byte) (((profile - 1) << 6) + (freqIdx << 2) + (chanCfg >> 2));
            packet[3] = (byte) (((chanCfg & 3) << 6) + (packetLen >> 11));
            packet[4] = (byte) ((packetLen & 0x7FF) >> 3);
            packet[5] = (byte) (((packetLen & 7) << 5) + 0x1F);
            packet[6] = (byte) 0xFC;
        }

          这是音频编码中非常重要的一个写入ADTS头部数据的操作,具体的原因,请查阅相关资料,我们这里就不多说了,其实大概就是用几个字符来表示我们音频的格式,采样率等等信息。
          通过上面几个步骤,我们就完成了将一个很大的音频的原始数据PCM进行编码,然后写入到一个音频文件里面的操作。

          可能有童鞋就会问了,那你这样把音频解码—>PCM—>编码成音频,有什么意义呢?其实这些非常有意义的事情,比如基于上面的这些功能,我们就可以完成很多的音频操作的功能了,比如我们可以把两个音频分别进行解码,然后依次读取PCM文件中的数据进行编码,从而实现两个甚至多个音频的连接,或者是音频的变声功能,将正常的声音进行变调,从而实现变声器的功能,再或者是将两个音频进行混音,也就是基于同一时间戳进行播放。

          看到音频混音,可能有童鞋就不太明白要怎么实现了,那么下面的这部分内容,我们就来讲解音频混音的核心原理。

     

    音频的混音(单声道和双声道的区别)

          这部分,我们来讲解一些音频混音的核心原理,所谓音频混音就是将两个甚至多个音频基于同一个时间戳,同时进行播放,在android中可以通过实例化多个音频播放器来实现,但是如何能编码成一个文件呢?

          在上面讲解PCM部分知识的时候,我们已经了解到了其实声音在计算机系统中的存在形式其实就是一系列的byte数据,至于这些byte数据是如何通过离散数学转换来的,我们这里就不进行深入研究了。但是我们可以明白的是,我们都拿到最原始的byte数据了,那么自然可以对他进行处理了,而所谓的音频混音,其实就是一种处理。这样的音频处理算法其实在c/c++上面已经有非常多的很好的实现了,这不过我们这里用java来实现一遍。

           其实音频混音的核心原理就是将两个音频的原始byte数据进行叠加,非常简单的 + 起来,比如某个位置的数据是1 而另一个音频同样位置是2 加起来就是3,这样就完成了音频的混音,当然这是最基础也是最垃圾的混音算法,我们这里会介绍其中的一种混音算法,基本上可以达到商业使用的。那就是归一化混音算法。

           他的基本公式是

       C = A + B - A * B / (数据类型的最大值);
       byte数据就是
       C = A + B - A * B / 127;
       short数据就是
       C = A + B - A * B / 32767;

           为什么要进行 后面的减去操作呢?如果你使用byte进行数据操作的话,非常容易就出现数据大于最大值,而这样的公式就是为了避免这样的情况出现,另外无法避免时,需要进行归一化,也就是如果超出了范围,就取最大值,如果小于了范围就取最小值
          具体的实现,如下

        /**
         * 归一化混音
         * */
        public static byte[] normalizationMix(byte[][] allAudioBytes){
            if (allAudioBytes == null || allAudioBytes.length == 0)
                return null;
    
            byte[] realMixAudio = allAudioBytes[0];
    
            //如果只有一个音频的话,就返回这个音频数据
            if(allAudioBytes.length == 1)
                return realMixAudio;
    
            //row 有几个音频要混音
            int row = realMixAudio.length /2;
            //
            short[][] sourecs = new short[allAudioBytes.length][row];
            for (int r = 0; r < 2; ++r) {
                for (int c = 0; c < row; ++c) {
                    sourecs[r][c] = (short) ((allAudioBytes[r][c * 2] & 0xff) | (allAudioBytes[r][c * 2 + 1] & 0xff) << 8);
                }
            }
    
            //coloum第一个音频长度 / 2
            short[] result = new short[row];
            //转成short再计算的原因是,提供精确度,高端的混音软件据说都是这样做的,可以测试一下不转short直接计算的混音结果
            for (int i = 0; i < row; i++) {
                int a = sourecs[0][i] ;
                int b = sourecs[1][i] ;
                if (a <0 && b<0){
                    int i1 = a  + b  - a  * b / (-32768);
                    if (i1 > 32767){
                        result[i] = 32767;
                    }else if (i1 < - 32768){
                        result[i] = -32768;
                    }else {
                        result[i] = (short) i1;
                    }
                }else if (a > 0 && b> 0){
                    int i1 = a + b - a  * b  / 32767;
                    if (i1 > 32767){
                        result[i] = 32767;
                    }else if (i1 < - 32768){
                        result[i] = -32768;
                    }else {
                        result[i] = (short) i1;
                    }
                }else {
                    int i1 = a + b ;
                    if (i1 > 32767){
                        result[i] = 32767;
                    }else if (i1 < - 32768){
                        result[i] = -32768;
                    }else {
                        result[i] = (short) i1;
                    }
                }
            }
            return toByteArray(result);
        }
        public static byte[] toByteArray(short[] src) {
            int count = src.length;
            byte[] dest = new byte[count << 1];
            for (int i = 0; i < count; i++) {
                dest[i * 2 +1] = (byte) ((src[i] & 0xFF00) >> 8);
                dest[i * 2] = (byte) ((src[i] & 0x00FF));
            }
            return dest;
        }

        上面代码,就是一个归一化混音算法的java实现,而我们在过程中将原始是byte数据转换成了short数据的原因就是为了提高精度,从而让混音效果更好。核心原理就是上面的公式。

         当然因为音频的原始数据其实是非常多的,为了提升效率,最好使用jni实现混音相关算法,这样就可以实现一个效果较好的混音算法了。项目里面已添加相关实现,可以进行测试查阅。

         但是混音时,有一个问题需要注意一下,就是音频是存在单声道和双声道,立体声的区别的。我们读取音频的信息的时候,可以看到他们是哪种声道的,单声道(mono),双声道(stereo),其实stereo应该叫立体声,但是我查阅资料得到的信息是,大部分的android手机其实是不支持立体声录音的,android平台的很多立体声其实只是单纯的双声道,因为这涉及到非常底层的知识了,我也不太了解这一点,有知道的朋友,还望不惜赐教。这里在进行混音的时候,不同的声道会出现问题,因为不同的声道数据同样的时间戳,播放的数据量是不同的,但是我们这里混音是按照数据量来混的,所以一个单声道和一个多声道的音频直接混音的话,就会出现混音失败。那么如何解决这个问题呢?

         其实我们可以通过将mono转成stereo的方法来解决这个问题,居然的实现很简单,如下代码

      for (int i = 0; i < monoBytes.length; i += 2) {
                stereoBytes[i*2+0] = monoBytes[i];
                stereoBytes[i*2+1] = monoBytes[i+1];
                stereoBytes[i*2+2] = monoBytes[i];
                stereoBytes[i*2+3] = monoBytes[i+1];
      }

        

    音频的音量调节

         在上面,我们实现了音频的混音,但是如果我们想要调节音频原始音量的大小(不是通过手机音量键调节),我们应该怎么做呢,比如如果要让你实现给视频增加bgm的时候,你bgm的音量不能太大以至于原视频声音听不见了。其实所谓音量调节,还是很简单,就是原始byte数据 乘上一定范围内的数值,即可实现该功能。

         公式

        C = A * vol;//vol的取值范围 通常是小于10,大于0的,如果是0的话,就没有声音了,如果太大了就会出现杂音

        vol的具体取值范围需要自己多去测试,当然 这是最简单的一种实现方式。

     

    总结

        到这里的话,本篇文章就基本上结束了,我们来回顾一下主要内容,首先,我们实现了从视频里面分离出音频文件,然后将音频文件解码成最原始的PCM数据,再通过android平台的硬编码,将PCM数据的文件重新编码成一个可以播放的音频文件,接下来主要是讲解了一些音频混音的一些知识,实现了一个归一化混音算法,然后说了一下不同声道音频的需要注意的一些问题,最后实现了一个改变原始音频音量大小的功能。

        那么,下一篇,按照计划,我们将要通过OpenGL实现android平台的视频拼接功能,将不同的视频完美的拼接在一起,当然并不是像音频这样的混音,而是在第一个视频播放完毕之后,接着播放第二个视频这种,而不是一个画面中播放两个视频。

        因为个人水平有限,难免有错误和不足之处,还望大家能包涵和提醒。谢谢啦!!!

     

    其他

         相关代码都已经更新到github上面了,项目的github地址,麻烦顺手给个star,谢谢啦~

           VideoEditor-For-Android

     

     

     

     

    展开全文
  • 可任意分割、截取、融合音频信息,方便手机铃音
  • matlab 中的实时音频音频系统工具箱™针对实时音频处理进行了优化. audioDeviceReader, audioDeviceWriter, audioPlayerRecorder,dsp.AudioFileReader 和 dsp.AudioFileWriter 器是为流式传输多通道音频而设计的, ...

    matlab 中的实时音频

    音频系统工具箱™针对实时音频处理进行了优化. audioDeviceReader, audioDeviceWriter, audioPlayerRecorder,dsp.AudioFileReader 和 dsp.AudioFileWriter 器是为流式传输多通道音频而设计的, 它们提供了必要的参数, 以便您可以在吞吐量和延迟之间进行权衡.

    有关实时处理的信息以及如何优化算法的提示, 请参阅音频 iseo: 缓冲, 延迟和吞吐量.

    本教程介绍如何在 matlab 中实现音频流处理 ®. 它概述了创建开发测试台的工作流, 并提供了工作流每个阶段的示例.

    创建开发试验台

    本教程通过四个步骤创建开发测试台:

    生成对象以从测试台输入和输出音频.

    创建一个音频流循环, 逐帧处理音频帧.

    添加一个范围, 以可视化音频流循环的输入和输出.

    为音频流循环添加处理算法.

    本教程还讨论了实时可视化和调整处理算法的工具.

    ab7653affab982b574eb7acc55df2e04.gif

    有关处理循环的概述, 请考虑下面完成的测试. 您可以通过逐步完成本教程来重新创建此测试台.

    ab7653affab982b574eb7acc55df2e04.gif

    1. 创建 input / 输出系统对象 s

    音频流循环可以从设备或文件中读取, 并且可以写入设备或文件. 在本例中, 您将构建一个音频流循环, 该循环从文件逐帧读取音频帧, 并将音频帧写入设备. 有关可选的输入 / 输出配置.

    创建 dsp.AudioFileReader 指定一个文件. 若要减少延迟, 请设置 dsp 的 SamplesPerFramedsp.AudioFileReader.

    接下来, 创建 audioDeviceWriter system 对象, 并将其采样率指定为输入系统对象的采样率.

    有关如何使用系统对象的详细信息, 请参阅什么是系统对象?(matlab)

    2. 创建音频流循环

    音频流循环以迭代方式处理音频. 它通过以下方式这样做:

    读取音频信号的帧

    处理该帧的音频信号

    将音频信号的帧写入设备或文件

    移动到下一帧

    在本教程中, 从文件中读取音频流循环的输入. 输出将写入设备.

    要逐帧读取音频文件, 请调用 dsp.AudioFileReader, 并且不提供任何参数. 要逐帧写入音频信号, 请在音频流循环中调用音频 audioDeviceWriter

    所有系统对象都具有 release 功能. 作为最佳实践, 请在使用后释放系统对象, 尤其是当这些系统对象与硬件设备 (如声卡) 通信时.

    3. 添加范围

    音频系统工具箱用户可以使用多个作用域. 两个常见的作用域是 dsp.TimeScope 和 dsp.SpectrumAnalyzer. 本教程使用 dsp.TimeScope 音频信号的时间范围系统对象.

    dsp.TimeScope 中显示音频信号. 创建系统对象. 若要帮助可视化, 请指定 TimeSpan, BufferLength"和 YLimits" 属性的值. 要逐帧显示音频信号, 请调用 dsp.TimeScope 流循环中的时间范围系统对象, 其中包含音频信号作为参数.

    4. 开发处理算法

    在大多数应用程序中, 您希望在音频流循环中处理音频信号. 处理阶段可以是:

    音频流循环中的 matlab 代码块

    在音频流循环中调用的单独函数

    音频流循环中调用的系统对象

    在本教程中, 您将调用 reverberator 系统对象来处理音频流循环中的信号.

    创建 reverberator 系统对象, 并将 SampleRate 属性指定为输入系统对象的采样率. 若要调整混响效果, 请指定 PreDelay 和 WetDryMix 属性的值. 若要将混响效果应用于音频信号帧逐帧, 请在音频流循环中调用 reverberator 系统对象, 并将音频信号作为参数.

    添加可调谐性

    音频系统工具箱用户有多个选项可将实时可调性添加到处理算法中. 要将可调性添加到音频流循环, 可以使用:

    音频测试台 - 基于 audioPlugin, 适用于音频插件类和大多数音频系统工具箱系统对象.

    内置功能 - 音频系统工具箱中用于可视化处理算法的关键方面的功能.

    自定义的用户界面 - 请参阅教程的实时参数优化.

    midi 控制器 - 许多音频系统工具箱系统对象包括支持 midi 控件的功能. 您可以使用 reverberator 系统对象中的 configureMIDI 函数将系统对象属性同步到 midi 控件. 要将 midi 控件与没有 configureMIDI 系统对象一起使用, 请参阅 midi 控制表面接口.

    用户数据报协议 (udp) - 您可以在 matlab 中使用 udp 进行无连接传输. 您还可以使用 udp 在环境之间接收或传输数据报. 可能的应用包括使用 matlab 工具在第三方环境中播放和可视化音频时调整音频处理算法. 有关 udp 通信的应用示例, 请参阅使用 udp 在 daw 和 matlab 之间进行通信.

    音频系统工具箱™针对实时音频处理进行了优化. audioDeviceReader, audioDeviceWriter, audioPlayerRecorder,dsp.AudioFileReader 和 dsp.AudioFileWriter 器是为流式传输多通道音频而设计的, 它们提供了必要的参数, 以便您可以在吞吐量和延迟之间进行权衡.

    有关实时处理的信息以及如何优化算法的提示, 请参阅音频 iseo: 缓冲, 延迟和吞吐量.

    本教程介绍如何在 matlab 中实现音频流处理 ®. 它概述了创建开发测试台的工作流, 并提供了工作流每个阶段的示例.

    创建开发试验台

    本教程通过四个步骤创建开发测试台:

    生成对象以从测试台输入和输出音频.

    创建一个音频流循环, 逐帧处理音频帧.

    添加一个范围, 以可视化音频流循环的输入和输出.

    为音频流循环添加处理算法.

    本教程还讨论了实时可视化和调整处理算法的工具.

    ab7653affab982b574eb7acc55df2e04.gif

    有关处理循环的概述, 请考虑下面完成的测试. 您可以通过逐步完成本教程来重新创建此测试台.

    ab7653affab982b574eb7acc55df2e04.gif

    1. 创建 input / 输出系统对象 s

    音频流循环可以从设备或文件中读取, 并且可以写入设备或文件. 在本例中, 您将构建一个音频流循环, 该循环从文件逐帧读取音频帧, 并将音频帧写入设备. 有关可选的输入 / 输出配置, 请参阅快速入门示例.

    创建 dsp.AudioFileReader 指定一个文件. 若要减少延迟, 请设置 dsp 的 SamplesPerFramedsp.AudioFileReader.

    接下来, 创建 audioDeviceWriter system 对象, 并将其采样率指定为输入系统对象的采样率.

    有关如何使用系统对象的详细信息, 请参阅什么是系统对象?(matlab)

    2. 创建音频流循环

    音频流循环以迭代方式处理音频. 它通过以下方式这样做:

    读取音频信号的帧

    处理该帧的音频信号

    将音频信号的帧写入设备或文件

    移动到下一帧

    在本教程中, 从文件中读取音频流循环的输入. 输出将写入设备.

    要逐帧读取音频文件, 请调用 dsp.AudioFileReader, 并且不提供任何参数. 要逐帧写入音频信号, 请在音频流循环中调用音频 audioDeviceWriter

    所有系统对象都具有 release 功能. 作为最佳实践, 请在使用后释放系统对象, 尤其是当这些系统对象与硬件设备 (如声卡) 通信时.

    3. 添加范围

    音频系统工具箱用户可以使用多个作用域. 两个常见的作用域是 dsp.TimeScope 和 dsp.SpectrumAnalyzer. 本教程使用 dsp.TimeScope 音频信号的时间范围系统对象.

    dsp.TimeScope 中显示音频信号. 创建系统对象. 若要帮助可视化, 请指定 TimeSpan, BufferLength"和 YLimits" 属性的值. 要逐帧显示音频信号, 请调用 dsp.TimeScope 流循环中的时间范围系统对象, 其中包含音频信号作为参数.

    4. 开发处理算法

    在大多数应用程序中, 您希望在音频流循环中处理音频信号. 处理阶段可以是:

    音频流循环中的 matlab 代码块

    在音频流循环中调用的单独函数

    音频流循环中调用的系统对象

    在本教程中, 您将调用 reverberator 系统对象来处理音频流循环中的信号.

    创建 reverberator 系统对象, 并将 SampleRate 属性指定为输入系统对象的采样率. 若要调整混响效果, 请指定 PreDelay 和 WetDryMix 属性的值. 若要将混响效果应用于音频信号帧逐帧, 请在音频流循环中调用 reverberator 系统对象, 并将音频信号作为参数.

    添加可调谐性

    音频系统工具箱用户有多个选项可将实时可调性添加到处理算法中. 要将可调性添加到音频流循环, 可以使用:

    音频测试台 - 基于 audioPlugin, 适用于音频插件类和大多数音频系统工具箱系统对象.

    内置功能 - 音频系统工具箱中用于可视化处理算法的关键方面的功能.

    自定义的用户界面 - 请参阅教程的实时参数优化.

    midi 控制器 - 许多音频系统工具箱系统对象包括支持 midi 控件的功能. 您可以使用 reverberator 系统对象中的 configureMIDI 函数将系统对象属性同步到 midi 控件. 要将 midi 控件与没有 configureMIDI 系统对象一起使用, 请参阅 midi 控制表面接口.

    用户数据报协议 (udp) - 您可以在 matlab 中使用 udp 进行无连接传输. 您还可以使用 udp 在环境之间接收或传输数据报. 可能的应用包括使用 matlab 工具在第三方环境中播放和可视化音频时调整音频处理算法. 有关 udp 通信的应用示例, 请参阅使用 udp 在 daw 和 matlab 之间进行通信.

    关注公众号: MATLAB 基于模型的设计 (ID:xaxymaker) , 每天推送 MATLAB 学习最常见的问题, 每天进步一点点, 业精于勤荒于嬉.

    ab7653affab982b574eb7acc55df2e04.gif

    来源: https://www.cnblogs.com/52geek/p/10453551.html

    展开全文
  • 第1章 音频系统 转载请注明:LXS, http://blog.csdn.net/uiop78uiop78/article/details/8787779 对于一部嵌入式设备来说,除了若干基础功能外(比如手机通话、短信),最重要的可能就是多媒体了——那么一个最简单...
  • 音频格式

    2018-02-24 14:53:42
    https://www.cnblogs.com/samirchen/p/7071824.htmlhttps://www.cnblogs.com/my_life/articles/6835695.htmlhttp://www.cnblogs.com/my_life/articles/6842155.html音频在数字音频领域,常用的采样率有:8,000 Hz -...
  • PCM音频编码

    万次阅读 2017-12-27 17:30:00
    PCM语音编码主要过程是...有一定电子基础的都知道传感器采集音频信号是模拟量,而我们实际传输过程中使用的是数字量。而这就涉及到模拟转数字的过程,下面将进行介绍。1 PCM编码原理PCM 脉冲编码调制是Pulse Code Modul
  • 音频连接线,简称音频线,用来传输电声信号或数据的线。广义的来说有电信号与光信号两大类。 由音频电缆和连接头两部分组成,其中:音频电缆一般为双芯屏蔽电缆,连接头常见的有RCA(俗称莲花头)、XLR(俗称卡侬头)、...
  • 音频资料

    千次阅读 2015-06-11 11:41:47
    这是我在网上看到的关于各种音频格式最全的一个帖子,特地转载过来,供大家参考。在些对收集者和各位作者表示真诚的感谢。 1、WAV文件:采样率(Sample Rate),深度(bit-depth)WAV文件可以说是最原始的数字化...
  • 如果你想做音频引流,请耐心的看完,文章对音频引流进行了一个全面的分享并且兴起博客还为看到最后的小伙伴准备了一份精准引流秘籍 有人喜欢短视频,有人喜欢图文,也有人喜欢听书做乐。 喜马拉雅,一个被选择性忽略...
  • 本篇开始讲解在Android平台上进行的音频编辑开发,首先需要对音频相关概念有基础的认识。所以本篇要讲解以下内容: 常用音频格式简介 WAV和PCM的区别和联系 WAV文件头信息 采样率简介 声道数和采样位数下的PCM...
  • 完整音频播放器

    千次阅读 2018-02-05 09:48:47
    1.关于音频播放器基本功能 1.1 基本实现的功能 1.2 音频的缓存,下载,播放权限等功能 1.3 音频后台播放功能 1.3.1 音频播放可以支持后台播放 1.3.2 Android系统有自动回收内存机制 1.4 需要注意的问题 1.4.1 一.....
  • 到目前为止,我们这个系列已经讨论了文件格式和数据格式的区别,还有怎么样在Mac下面转换和录制音频文件。现在我们将会进行比较有趣的部分——在你的iphone上面播放音频! 在Mac上面有许多种方式播放音频——...
  • 文章目录一、音频基础知识1、声音的三要素2、音频的...人耳听觉音频范围是20Hz-20000Hz(做音频压缩时不在这个范围内的数据就可以砍掉)。 (2)音量 也就是响度。人耳对声音强弱的主观感觉称为响度。响度和声波振动的
  • 音频基础知识

    2017-06-29 16:26:38
    音频,英文是AUDIO,也许你会在录像机或VCD的背板上看到过AUDIO输出或输入口。这样我们可以很通俗地解释音频,只要是我们听得见的声音,就可以作为音频信号进行传输。有关音频的物理属性由于过于专业,请大家参考...
  • Android SDK 提供了两套播放音频的API,分别是:MediaPlayer和AudioTrack。两者还是有非常大的差别的。MediaPlayer会在framework层创建相应的音频解码器。所以能够播放多种格式的声音文件。比如MP3。AAC,WAV,OGG。...
  • 怎样把视频中的音频提取成mp3?

    千次阅读 2020-11-30 18:14:13
    视频由由音频和图像组成,有时我们在观看一些视频时,经常会听到一些非常好听的背景音乐,想要保存成mp3 时,发现很多音乐平台要么是付费下载的,要么就是没有合适的版本,那么如何把视频里的背景音乐提取出来呢,...
  • 怎样为Mac视频添加音频

    千次阅读 2020-04-23 16:41:52
    本文为大家带来的就是在Mac上为视频添加音频的教程。 如何在Mac上的视频中添加音频 VLC 大多数人都不知道VLC不仅是媒体播放器,而且还是视频编辑器,它允许您向视频添加音乐。这个开源的跨平台多媒体播放器几乎支持...
  • 者注:这是我最喜欢的iPhone OpenAL教程之一,总共有好几篇文章,我会逐步翻译。随着保密协议的解除,我们可以开始公开讨论iPhone的代码了。...今天我想谈谈OpenAL今天我只讨论不到30秒的音频以及音效
  • 音频采样

    千次阅读 2014-03-20 10:46:20
    音频采样 数码音频系统是通过将声波波形转换成一连串的二进制数据来再现原始声音的,实现这个步骤使用的设备是模/数转换器(A/D)它以每秒上万次的速率对声波进行采样,每一次采样都记录下了原始模拟声波在某一...
  • 音频知识及音频格式详解 来源: 张超的日志 ============= 一、名词解释 ============= 【比特率】 这个词有多种翻译,比如码率等,表示经过编码(压缩)后的音频数据每秒钟需要用多少个比特来...
  • 音频杂项

    2015-04-01 10:45:51
    1. 音频,视频同步是靠什么,pts吗?怎样计算的 27M 时钟被300分频后为单位,即90K hz  现有的标清机顶盒只能一次调谐一个频点,这个频点以前只能播出一个模拟节目,现在能包含6到8个数字频道, 但解码器只能一...
  • iis音频

    千次阅读 2010-01-28 16:04:00
    IIS音频技术 2009-10-05 21:26 阅读114 评论0 字号: 大大 中中 小小 1 IIS总线 IIS(Integrate Interface of Sound)即集成音频接口,在上个世纪80年代首先被Philips公司用于消费产品的音频设备,并在一个称为...
  • Android音频API

    2021-09-15 14:29:11
    Android系统提供了四个层面的音频API: Java层MediaRecorder&MediaPlayer系列; Java层AudioTrack&AudioRecorder系列; Jni层opensles; JNI层AAudio(Android O引入) 下面分别介绍这些API的使用及特点。...
  • 1、首先,音频播放的实现,我这里使用的是AVPlayer。 AVAudioPlayer只能播放本地资源。当然还有别的播放方法这里就不列举了。 以下代码实现的是如下图所示的效果,点击图标可以暂停或者继续播放: 需要的属性:...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 30,941
精华内容 12,376
关键字:

怎样做音频