From d153640d6abf453a0293cbd7298444a727ef2df3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?whalensun=28=E5=AD=99=E4=BC=9F=29?= <532125505@qq.com> Date: Wed, 22 Nov 2023 15:39:58 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=94=B9gvoice=E7=9A=843A=E5=A4=84?= =?UTF-8?q?=E7=90=86=E6=8E=A5=E5=8F=A3.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Change-Id: I9126266870b20abf982228fe42b9cd34615433b3 --- sdk/video-link-android/build.gradle | 3 + .../link/util/audio/AudioRecordUtil.java | 70 +++++++++++++++---- .../video/preview/VideoPreviewActivity.kt | 61 ++-------------- 3 files changed, 65 insertions(+), 69 deletions(-) diff --git a/sdk/video-link-android/build.gradle b/sdk/video-link-android/build.gradle index 1d11606f2..a8f3dc6ee 100644 --- a/sdk/video-link-android/build.gradle +++ b/sdk/video-link-android/build.gradle @@ -54,6 +54,9 @@ dependencies { // } // api 'com.tencent.iot.thirdparty.android:xp2p-sdk:2.4.23' api 'com.tencent.iot.thirdparty.android:xp2p-sdk:2.4.41-SNAPSHOT' + api 'com.tencent.iot.thirdparty.android:ijkplayer-java:2.0.11' + api 'com.tencent.iot.thirdparty.android:ijkplayer-armv7a:2.0.11' + api 'com.tencent.iot.thirdparty.android:ijkplayer-arm64:2.0.11' api 'com.tencent.iot.thirdparty.android:media-server:1.0.5' api 'io.github.sundoggynew:iot-soundtouch:1.0.0' api 'io.github.sundoggynew:iot-voice-changer:1.0.0' diff --git a/sdk/video-link-android/src/main/java/com/tencent/iot/video/link/util/audio/AudioRecordUtil.java b/sdk/video-link-android/src/main/java/com/tencent/iot/video/link/util/audio/AudioRecordUtil.java index 3bf76dc6f..ae438eddf 100644 --- a/sdk/video-link-android/src/main/java/com/tencent/iot/video/link/util/audio/AudioRecordUtil.java +++ b/sdk/video-link-android/src/main/java/com/tencent/iot/video/link/util/audio/AudioRecordUtil.java @@ -24,12 +24,17 @@ import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; +import java.util.ArrayList; +import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import java.util.concurrent.LinkedBlockingDeque; import java.util.concurrent.atomic.AtomicInteger; import com.iot.gvoice.interfaces.GvoiceJNIBridge; +import tv.danmaku.ijk.media.player.IjkMediaPlayer; + public class AudioRecordUtil implements EncoderListener, FLVListener { private static final int DEFAULT_CHANNEL_CONFIG = AudioFormat.CHANNEL_IN_STEREO; //设置音频的录制的声道CHANNEL_IN_STEREO为双声道,CHANNEL_CONFIGURATION_MONO为单声道 @@ -68,7 +73,8 @@ public class AudioRecordUtil implements EncoderListener, FLVListener { private static final int SAVE_PCM_DATA = 1; - private OnReadAECProcessedPcmListener mAECProcessedPcmListener; + private IjkMediaPlayer player; + private LinkedBlockingDeque playPcmData = new LinkedBlockingDeque<>(); // 内存队列,用于缓存获取到的播放器音频pcm private class MyHandler extends Handler { @@ -135,15 +141,6 @@ public AudioRecordUtil(Context ctx, String id, int sampleRate, int channel, int this.enableAGC = enableAGC; init(sampleRate, channel, bitDepth); } - public AudioRecordUtil(Context ctx, String id, int sampleRate, int channel, int bitDepth, int pitch, boolean enableAEC, boolean enableAGC, OnReadAECProcessedPcmListener listener) { - context = ctx; - deviceId = id; - this.pitch = pitch; - this.enableAEC = enableAEC; - this.enableAGC = enableAGC; - mAECProcessedPcmListener = listener; - init(sampleRate, channel, bitDepth); - } private void init(int sampleRate, int channel, int bitDepth) { recordMinBufferSize = AudioRecord.getMinBufferSize(sampleRate, channel, bitDepth); @@ -232,6 +229,10 @@ public void setMode(VoiceChangerMode mode) { } } + public void setPlayer(IjkMediaPlayer player) { + this.player = player; + } + /** * 开始录制 */ @@ -241,6 +242,9 @@ public void start() { fos2 = createFiles("far"); fos3 = createFiles("aec"); } + if (!playPcmData.isEmpty()) { + playPcmData.clear(); + } GvoiceJNIBridge.init(); reset(); if (!VoiceChangerJNIBridge.isAvailable()) { @@ -254,6 +258,7 @@ public void start() { recorderState = true; audioRecord.startRecording(); new RecordThread().start(); + new WriteThread().start(); } private void reset() { @@ -382,8 +387,8 @@ public void run() { if (AudioRecord.ERROR_INVALID_OPERATION != read) { //获取到的pcm数据就是buffer了 if (buffer != null && pcmEncoder != null) { - if (mAECProcessedPcmListener != null) { - byte [] playerPcmBytes = mAECProcessedPcmListener.onReadAECProcessedPcmListener(buffer.length); + if (player != null && player.isPlaying()) { + byte [] playerPcmBytes = onReadPlayerPlayPcm(buffer.length); byte[] aecPcmBytes = GvoiceJNIBridge.cancellation(buffer, playerPcmBytes); if (isRecord) { writePcmBytesToFile(buffer, playerPcmBytes, aecPcmBytes); @@ -430,4 +435,45 @@ private boolean initAGC(int audioSession) { control.setEnabled(true); return control.getEnabled(); } + + private byte[] onReadPlayerPlayPcm(int length) { + if (playPcmData.size() > length) { + byte[] res = new byte[length]; + try { + for (int i = 0 ; i < length ; i++) { + res[i] = playPcmData.take(); + } + } catch (InterruptedException e) { + e.printStackTrace(); + } + Log.e(TAG, "onReadPlayerPlayPcm playPcmData.length: " + playPcmData.size()); + if (playPcmData.size()>20000) { + playPcmData.clear(); + } + return res; + } else { + return new byte[length]; + } + } + + private class WriteThread extends Thread { + @Override + public void run() { + while (recorderState) { + if (player != null && player.isPlaying()) { + byte[] data = new byte[204800]; + int len = player._getPcmData(data); + if (len > 0) { + byte[] playerBytes = new byte[len]; + System.arraycopy(data, 0, playerBytes, 0, len); + List tmpList = new ArrayList<>(); + for (byte b : playerBytes) { + tmpList.add(b); + } + playPcmData.addAll(tmpList); + } + } + } + } + } } diff --git a/sdkdemo/src/main/java/com/tencent/iot/explorer/link/demo/video/preview/VideoPreviewActivity.kt b/sdkdemo/src/main/java/com/tencent/iot/explorer/link/demo/video/preview/VideoPreviewActivity.kt index 0577c51af..3e61bc538 100644 --- a/sdkdemo/src/main/java/com/tencent/iot/explorer/link/demo/video/preview/VideoPreviewActivity.kt +++ b/sdkdemo/src/main/java/com/tencent/iot/explorer/link/demo/video/preview/VideoPreviewActivity.kt @@ -66,8 +66,7 @@ private var keepPlayThreadLock = Object() private var keepAliveThreadRuning = true class VideoPreviewActivity : VideoBaseActivity(), EventView, TextureView.SurfaceTextureListener, - XP2PCallback, CoroutineScope by MainScope(), VolumeChangeObserver.VolumeChangeListener, - OnReadAECProcessedPcmListener { + XP2PCallback, CoroutineScope by MainScope(), VolumeChangeObserver.VolumeChangeListener{ open var tag = VideoPreviewActivity::class.simpleName var orientationV = true @@ -93,8 +92,6 @@ class VideoPreviewActivity : VideoBaseActivity(), EventView, TextureView.Surface var startShowVideoTime = 0L @Volatile var showVideoTime = 0L - @Volatile - var recorderState = false //录制状态 var volumeChangeObserver: VolumeChangeObserver? = null val MSG_UPDATE_HUD = 1 @@ -102,8 +99,6 @@ class VideoPreviewActivity : VideoBaseActivity(), EventView, TextureView.Surface var screenHeight = 0 var firstIn = true - var playPcmData = LinkedBlockingDeque() // 内存队列,用于缓存获取到的播放器音频pcm - override fun getContentView(): Int { return R.layout.activity_video_preview } @@ -144,9 +139,9 @@ class VideoPreviewActivity : VideoBaseActivity(), EventView, TextureView.Surface presenter.getEventsData(Date()) tv_event_status.visibility = View.VISIBLE tv_event_status.setText(R.string.loading) -// audioRecordUtil = AudioRecordUtil(this, "${it.productId}/${presenter.getDeviceName()}", 16000, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT) + audioRecordUtil = AudioRecordUtil(this, "${it.productId}/${presenter.getDeviceName()}", 16000, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT) // //变调可以传入pitch参数 - audioRecordUtil = AudioRecordUtil(this, "${it.productId}/${presenter.getDeviceName()}", 16000, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT, 0, false, false, this) +// audioRecordUtil = AudioRecordUtil(this, "${it.productId}/${presenter.getDeviceName()}", 16000, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT, -6) audioRecordUtil.recordSpeakFlv(true) } @@ -275,18 +270,13 @@ class VideoPreviewActivity : VideoBaseActivity(), EventView, TextureView.Surface JSONArray.parseArray(repStatus, DevStatus::class.java)?.let { if (it.size == 1 && it.get(0).status == 0) { XP2P.runSendService("${accessInfo.productId}/${presenter.getDeviceName()}", Command.getTwoWayRadio(presenter.getChannel()), true) - recorderState = true - if (!playPcmData.isEmpty()) { - playPcmData.clear() - } + audioRecordUtil.setPlayer(player) audioRecordUtil.start() - startWriterThread() return true } } } else { - recorderState = false audioRecordUtil.stop() XP2P.stopSendService("${accessInfo.productId}/${presenter.getDeviceName()}", null) return true @@ -741,47 +731,4 @@ class VideoPreviewActivity : VideoBaseActivity(), EventView, TextureView.Surface CommonUtils.formatedSpeed(tcpSpeed, 1000)) tv_video_w_h?.text = "${player.videoWidth} x ${player.videoHeight}" } - - override fun onReadAECProcessedPcmListener(length: Int): ByteArray? { - return onReadPlayerPlayPcm(length) - } - - private fun onReadPlayerPlayPcm(length: Int): ByteArray? { - if (player != null && player.isPlaying) { - return if (playPcmData.size > length) { - val res = ByteArray(length) - try { - for (i in 0 until length) { - res[i] = playPcmData.take() - } - } catch (e: InterruptedException) { - e.printStackTrace() - } - res - } else { - ByteArray(length) - } - } - return ByteArray(length) - } - - private fun startWriterThread() { - Thread { - while (recorderState) { - if (player != null && player.isPlaying) { - val data = ByteArray(204800) - var len = player._getPcmData(data) - if (len > 0) { - val playerBytes = ByteArray(len) - System.arraycopy(data, 0, playerBytes, 0, len) - val tmpList: MutableList = ArrayList() - for (b in playerBytes) { - tmpList.add(b) - } - playPcmData.addAll(tmpList) - } - } - } - }.start() - } } \ No newline at end of file