1. 程式人生 > >Android移植speex部分問題解決

Android移植speex部分問題解決

菜鳥進場,方圓十里,寸草不生

最近做一個語音聊天的專案,接觸到speex庫。小白一個,按照網上大神指的路一步一步的走下來,遇見一些錯,困了幾天,最後解決了,也給後面走這條路的小夥伴看看,避免走到這些我走過的坑。

第一部分:speex移植
將speex移植到Android上。
參考http://blog.csdn.net/qq_29078329/article/details/56287338 部落格一步一步的走就好了,作者寫得很詳細,傻瓜式的操作。至於配置ndk那個,網上的教程也很多,具體自己摸索,這個坑不多。

不過補充一點,上面部落格裡面有一句:
這裡寫圖片描述

當時看到的時候不太懂,現在看上去是我太笨了,但也提示一下怎麼改吧,Java_com_speex_util_SpeexUtil_open中的com_speex_util_SpeexUtil換成你放Speex這個方法互動類的包名,大小寫什麼的都不能錯。一共六個方法,每個方法都需要這樣換。

當生成.so之後,本來作者還有第二篇部落格來介紹怎麼用的,不過因為我的專案比較簡單,用不了那麼複雜就沒用。然後問題就出來了。

第二部分:問題解決

1.編碼沒問題,一解碼就報錯。

這裡寫圖片描述

以前就沒見過這樣的錯誤,百度翻譯了一下,說什麼空引用,那大概意思就是我們說的空指標吧。那就是我在解碼的時候傳進去的資料有問題撒。打斷點除錯,果然,因為是用的udp傳輸的,建立之後packet.getData()是空,所以我穿進去的就是空。那就乾脆寫個固定值,改好之後..

2.聲音播放出來一直嗶嗶嗶嗶嗶嗶嗶~~~~

直接把音訊檔案打印出來,發現編碼成功之後的byte【】後面的全是0,意思就是陣列減大的,有些更本就沒用。需要想辦法把不要的0去掉。發現AudioRecord的read方法有長度返回值,編碼方法也有長度返回值,解碼方法也有長度返回。好吧,那就截一下不就好咯,把返回資料的陣列通過system.copeArray()擷取指定長度。改好之後…

3.再不說話的時候沒事兒,一說話那邊放出來就是蛙叫聲。蛙叫聲?WTF

一直解決不了,這就是嚴重失真啊,說明壓縮與解壓就有問題,為什麼呢?難道speex庫有問題,那是不可能的,有問題人家不知道改嗎,肯定自己的問題。難道是壓縮質量太小?導致失真,改成最大!還是蛙叫。糾結……

列表內容

作者是看波形什麼的看出來的,哈哈哈,牛批,那就是說明可能是speex在編碼的時候導致的問題,speex庫是沒問題的,但編碼又出問題了,什麼意思?大概率是自己引數給錯了,然後網上找解決方法,發現有個某些部落格裡面在書寫speex_jni.cpp中open方法時候給了一段註釋:

/*
     * speex_nb_mode:窄帶模式
     * speex_wb_mode:寬頻模式
     * speex_uwb_mode:超寬頻模式
     */
enc_state = speex_encoder_init(&speex_nb_mode); // 初始化編碼器 dec_state = speex_decoder_init(&speex_nb_mode); // 初始化解碼器

寬頻?是電信還是移動?難道是頻寬?還是音訊的頻率區間?換來試試,一個一個試。試第二個,有點清楚了,試第三個,哦?此處掌聲….

以下是測試程式碼,能成功的


import android.Manifest;
import android.content.pm.PackageManager;
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioRecord;
import android.media.AudioTrack;
import android.media.MediaRecorder;
import android.os.Build;
import android.support.annotation.RequiresApi;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;

import com.zimoli.textproject.R;

import java.io.IOException;
import java.lang.reflect.Array;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.util.Arrays;

public class YuYingDemo extends AppCompatActivity {
    private Button begin, close;
    private EditText edittext, get, send;
    private int frequence = 8000; //錄製頻率,單位hz.這裡的值注意了,寫的不好,可能例項化AudioRecord物件的時候,會出錯。我開始寫成11025就不行。這取決於硬體裝置
    private int channelConfig = AudioFormat.CHANNEL_IN_MONO;
    private int audioEncoding = AudioFormat.ENCODING_PCM_16BIT;
    DatagramSocket socket = null;
    InetAddress serverAddress = null;
    private boolean type = true;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_yu_ying_demo);
        begin = (Button) findViewById(R.id.begin);
        close = (Button) findViewById(R.id.close);
        edittext = (EditText) findViewById(R.id.edittext);
        get = (EditText) findViewById(R.id.get);
        send = (EditText) findViewById(R.id.send);

        begin.setOnClickListener(new View.OnClickListener() {
            @RequiresApi(api = Build.VERSION_CODES.M)
            @Override
            public void onClick(View v) {//開始語音
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        if (ContextCompat.checkSelfPermission(YuYingDemo.this, Manifest.permission.RECORD_AUDIO)
                                != PackageManager.PERMISSION_GRANTED) {
                            ActivityCompat.requestPermissions(YuYingDemo.this, new String[]{Manifest.permission.RECORD_AUDIO}, 1);
                        } else {
                            if (thread.isAlive()) {
                            } else {
                                thread.start();
                            }
                            try {
                                type = true;
                                socket = new DatagramSocket(Integer.parseInt(send.getText().toString().trim()));
                                serverAddress = InetAddress.getByName(edittext.getText().toString());
                            } catch (Exception e) {
                                e.printStackTrace();
                                Log.e("建立傳送socket失敗", e.toString());
                            }
                            int bufferSize = AudioRecord.getMinBufferSize(frequence, channelConfig, audioEncoding); //獲取快取最小大小
                            /*AudioRecord recorder = new AudioRecord(
                                    MediaRecorder.AudioSource.MIC, frequence,
                                    channelConfig,
                                    audioEncoding,
                                    bufferSize);*/
                            AudioRecord recorder = new AudioRecord(MediaRecorder.AudioSource.VOICE_COMMUNICATION, 8000,
                                    AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT, bufferSize);
                            short[] data = new short[bufferSize];//建立接收音訊訊號的陣列
                            recorder.startRecording();
                            Speex speex = new Speex(); 
                            speex.init();//初始化speex
                            int a1 = speex.getFrameSize();//獲取壓縮大小,怎麼來的都不知道,反正有個返回值,還是定值
                            while (true) {
                                if (!type) {
                                    break;
                                }
                                int x = recorder.read(data, 0, bufferSize);//重點了,獲取音訊資料以及大小
                                short[] x1 = new short[x];
                                System.arraycopy(data, 0, x1, 0, x);//去除音訊後面的0
                                Log.e("錄音內容", Arrays.toString(x1));
                                byte[] data1 = new byte[a1];//建立壓縮之後資料的儲存陣列
                                int y = speex.encode(x1, 0, data1, x1.length);//壓縮
                                // new Speex().encode(data, 0, data1, data.length);
                                byte[] y1 = new byte[y];
                                System.arraycopy(data1, 0, y1, 0, y);//去除壓縮之後後面的0
                                Log.e("編碼後錄音內容", Arrays.toString(y1));
                                DatagramPacket packet = new DatagramPacket(y1, y1.length, serverAddress, Integer.parseInt(send.getText().toString().trim()));
                                try {
                                    if (socket != null) {
                                        socket.send(packet);//傳送
                                        //Log.e("傳送成功", "---------------------");
                                    }
                                } catch (IOException e) {
                                    e.printStackTrace();
                                }
                            }
                            recorder.release();
                        }
                    }
                }).start();
            }
        });
        close.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {//關閉語音
                type = false;
                thread.interrupt();
            }
        });
    }

    private Thread thread = new Thread(new Runnable() {
        @Override
        public void run() {
            if (!type) {
                return;
            }
            DatagramSocket socket = null;
            try {
                socket = new DatagramSocket(Integer.parseInt(get.getText().toString().trim()));
            } catch (SocketException e) {
                e.printStackTrace();
                Log.e("建立接收socket失敗", e.toString());
            }
            int bufferSize = AudioRecord.getMinBufferSize(frequence, channelConfig, audioEncoding);
            AudioTrack track = new AudioTrack(AudioManager.STREAM_MUSIC, 8000,
                    AudioFormat.CHANNEL_OUT_MONO, AudioFormat.ENCODING_PCM_16BIT,
                    bufferSize, AudioTrack.MODE_STREAM);
            track.play();
            Speex speex = new Speex();
            speex.init();
            byte[] data = new byte[110];//這個值是上面獲取壓縮之後音訊的大小,也就是上面的y,這個大小按道理是固定值
            DatagramPacket packet = new DatagramPacket(data, 110);
            while (true) {
                if (!type) {
                    break;
                }
                try {
                    if (socket != null) {
                        socket.receive(packet);
                        Log.e("收到的錄音內容", Arrays.toString(packet.getData()));
                        short[] data2 = new short[bufferSize];
                        int z = speex.decode(packet.getData(), data2, packet.getData().length);//解壓縮
                        short[] z1 = new short[z];
                        System.arraycopy(data2, 0, z1, 0, z);//解壓縮去0
                        Log.e("解碼後錄音內容", Arrays.toString(z1));
                        /*
                        short[] data1 = new short[bufferSize];
                        speex.decode(packet.getData(), data1, a);
                        Log.e("解碼錄音內容", Arrays.toString(data1));*/
                        track.write(z1, 0, z1.length);//播放
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            track.release();
        }
    });
}