Android移植speex部分问题解决

本文分享了作者在使用Speex库进行Android平台语音聊天项目的开发过程中遇到的问题及解决方案,包括speex库的移植、编码解码过程中的常见错误分析与调试技巧。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

菜鸟进场,方圆十里,寸草不生

最近做一个语音聊天的项目,接触到speex库。小白一个,按照网上大神指的路一步一步的走下来,遇见一些错,困了几天,最后解决了,也给后面走这条路的小伙伴看看,避免走到这些我走过的坑。

第一部分:speex移植
将speex移植到Android上。
参考http://blog.youkuaiyun.com/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库有问题,那是不可能的,有问题人家不知道改吗,肯定自己的问题。难道是压缩质量太小?导致失真,改成最大!还是蛙叫。纠结……

然后看到这个博客:http://blog.163.com/yuan_zhch/blog/static/193790046201193110358591/
这个博客打开有点慢,不过里面有句话

列表内容

作者是看波形什么的看出来的,哈哈哈,牛批,那就是说明可能是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();
        }
    });
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值