【问题标题】:Audio encoding issues in certain Android devices for online streaming playback某些 Android 设备中用于在线流式播放的音频编码问题
【发布时间】:2015-06-25 02:22:08
【问题描述】:

背景:

我正在开发一个 Android 移动应用程序,用户在应用程序中录制音频,然后将其上传到服务器,然后可以将其流式传输回社区。

到目前为止,我们一直使用 .WAV 格式,它可以输出相当大的文件大小,以便通过移动网络上传和流式传输。我正在努力转换为 .AAC 格式,但我了解到不同制造商会出现一些不同的问题。

问题:

  • 目前测试的所有 Android 手机 (API-15+) 都可以在 AAC 中录制音频。
  • 但是,三星设备不会使用 MediaPlayer 直接播放 AAC 音频文件。相反,它们需要以 AAC 编码,但输出格式为 .MP4
  • .MP4 容器中的 AAC 音频文件可以在所有经过测试的设备上完美录制和播放。
  • 文件可以正常上传到服务器。

那么这里是主要问题。将文件流式传输到音频播放器时,声音播放器无法解码仅在三星设备上录制的音频。摩托罗拉和 Nexus 手机上录制的 AAC 文件可以与以前的 WAV 文件完美互换。我可以直接在我的桌面上播放声音文件,如果我直接在网络浏览器中打开文件,它们都会播放。

当我使用 AudioContext 流式传输和解码音频时会发生此问题。我相信这个问题与三星音频编解码器缺少标头数据有关,但我在这个问题上投入了大量时间,但还没有找到可行的解决方案。这个问题看起来很简单,但我花了很多工作才弄清楚到底是什么问题。任何帮助深表感谢。

我看到的两种方法是: 1.修复Android应用源代码编码问题 2. 更正浏览器中的错误。我看过有关使用函数尝试重新同步流的帖子,但我不完全了解如何实现。

我认为最好的解决方案是#1。

我在浏览器中下载了两个示例文件并查看了原始数据以进行比较。 (原始数据快照http://i.imgur.com/tF6uHoO.png // 我没有足够的声誉来嵌入图像,但稍后会修改。)

在 Android 中录制代码 RecordActiviy.java

// FILE NAME 
mFileName = "file.mp4";

// INITIATE MEDIA RECORDER
mRecorder = new MediaRecorder();
mRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
mRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
mRecorder.setOutputFile(mFileName);
mRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
mRecorder.setAudioSamplingRate(48000);
mRecorder.setAudioEncodingBitRate(64000);

try {
    mRecorder.prepare();
    mRecorder.start();

    } catch (IOException e) {
        Log.e("AUDIO RECORDING", "prepare() failed");
        e.printStackTrace();
    }
}
...

getSound.php

...    
$File = $path . $filename;

switch($filetype){
case 'wav':
    //FILE TYPE WAV
    $mime_type = "audio/vnd.wave";
    header("Content-type: ".$mime_type);
    $test = readfile($file);
    break;

case 'mp4': 
    //FILE TYPE MP4
    //$mime_type = "audio/mp4";
    $mime_type = "audio/aac";
    header("Content-type: ".$mime_type);
    $test = readfile($file);
    break;
default:
    break;
}

soundPlayer.js

...
var player;

function loadPlayer() {
    player = new SoundPlayer();
    ..UI and other stuff..
}

// Build Sound Player Object
function SoundPlayer() {

this.arrayBuffer = null; //array buffer of audio data

this.context = new AudioContext(); //browser sound context

this.duration = null;   //Length of audio clip
this.source = null;   //Converted audio source
this.trueEnd = true;   //Check to see if sound ended due to completion
this.isPlaying = false;   //Check to see if sound is currently playing
this.t = 0;   //Update variable for time counter
this.startTime = null;   //Start time of audio is logged when playAudio()
this.byteData = null;

var sp = this; // sp = soundPlayer (Reference to SoundPlayer obj)

this.playAudio = function() {

    this.source = null;

    /* ArrayBuffer must be decoded each time play is called
     * due to the nature of a decoded AudioBuffer only 
     * being able to be played once */

    sp.context.decodeAudioData(sp.arrayBuffer, decoded); 
    sp.t = setInterval(updateProgress, 10);
};

this.loadSound = function(pid) {

    $.ajax({
        async: true,
        url: "getSound.php", 
        data: { soundID: sID },
        beforeSend: function(xhr) {

            //Allow for binary data
            xhr.overrideMimeType("text/plain; charset=x-user-defined");
        },
        success: function(dataAsString) {

            dataAsString = dataAsString.trim();

            if( dataAsString.localeCompare("") == 0 ){
                alert("Sound File Not Found");
                return;
            }

            // convert string to byte array 
            var bytes = [];
            var i, l = dataAsString.length;

            for (i = 0; i < l; ++i) {
                bytes.push(dataAsString.charCodeAt(i) & 0xFF);
            }

            sp.byteData = bytes;

            //Convert byte array into audio buffer
            var arrayBuffer = new ArrayBuffer(bytes.length);
            var bufferView = new Uint8Array(arrayBuffer);

            for (var i = 0; i < bytes.length; i++) {
                bufferView[i] = bytes[i];
            }

            sp.arrayBuffer = arrayBuffer;

            //get sound duration info
            sp.context.decodeAudioData(arrayBuffer, function(buffer) {

                sp.duration = buffer.duration;
                notify_UI_load_complete();

            }, function(error){
****            // WE GET TO THIS BLOCK IN THE BROWSER
****            // RETURNS UNDEFINED
                console.log("error decoding "+ arrayBuffer.length);
                console.log(arrayBuffer);
                console.log("error type "+ error);
            });

        },
        error: function(jqXHR, textStatus, errorThrown) {
            console.log("load error");
            console.log(textStatus, errorThrown);
            alert("Could not load specific sound file");
        }
    });
}
...

TL;DR Web Audio AudioContext 中的 Android 三星 AAC 音频流错误。网络音频很难。我们是否已经获得了一个通用标准。

【问题讨论】:

  • 您是否尝试过在桌面浏览器中播放三星录制的音频?您确实说过“文件”播放,但我并不清楚您指的是哪些文件。另外题外话:“我们能不能得到一个通用标准”:AAC 已经是一个定义明确且质量很好的标准。
  • @TimothyGu 很抱歉不清楚,但是三星文件在桌面上播放正常。流式传输文件时出现问题
  • 你的直播怎么样?我没有看到任何流式传输代码。
  • soundPlayer.js 中的 loadSound() 向 getSound.php 发送一个 ajax 请求,getSound.php 获取文件,将其写入输出缓冲区并将其发回。 loadSound 以字符串的形式获取数据,然后遍历块中的数据以生成 byteArray 缓冲区。这可能会帮助php.net/manual/en/function.readfile.php

标签: javascript android html5-audio audio-streaming android-mediarecorder


【解决方案1】:

音频文件包含元数据信息。在某些文件中,这出现在文件末尾。在这种情况下,这些文件只有在完全下载后才能播放,即无法流式传输。

要使这些文件可流式传输,您需要将该元数据信息移动到音频文件的开头。您可以使用 MP4Box:

MP4Box -v -inter 300 old.mp4 -out new.mp4

其中 300 是以毫秒为单位的交错。

【讨论】:

    猜你喜欢
    • 2017-02-16
    • 1970-01-01
    • 2012-09-26
    • 1970-01-01
    • 2019-05-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多