save real mp3

This commit is contained in:
Pavel Poley 2022-06-14 14:57:11 +03:00
parent 86e1619ec8
commit 00ea6208ec
7 changed files with 243 additions and 38 deletions

View File

@ -68,4 +68,5 @@ dependencies {
implementation 'androidx.documentfile:documentfile:1.0.1'
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation 'com.github.naman14:TAndroidLame:1.1'
}

View File

@ -28,7 +28,8 @@
android:requestLegacyExternalStorage="true"
android:roundIcon="@mipmap/ic_launcher"
android:supportsRtl="true"
android:theme="@style/AppTheme">
android:theme="@style/AppTheme"
tools:replace="android:label">
<activity
android:name=".activities.WidgetRecordDisplayConfigureActivity"

View File

@ -20,6 +20,7 @@ const val EXTENSION_OGG = 2
val BITRATES = arrayListOf(32000, 64000, 96000, 128000, 160000, 192000, 256000, 320000)
const val DEFAULT_BITRATE = 128000
const val SAMPLE_RATE = 44100
const val RECORDING_RUNNING = 0
const val RECORDING_STOPPED = 1

View File

@ -0,0 +1,126 @@
package com.simplemobiletools.voicerecorder.helpers
import android.annotation.SuppressLint
import android.content.Context
import android.media.AudioFormat
import android.media.AudioRecord
import android.media.MediaRecorder
import com.naman14.androidlame.AndroidLame
import com.naman14.androidlame.LameBuilder
import com.simplemobiletools.commons.helpers.ensureBackgroundThread
import com.simplemobiletools.voicerecorder.extensions.config
import com.simplemobiletools.voicerecorder.recorder.Recorder
import java.io.*
import java.util.concurrent.atomic.AtomicBoolean
import java.util.concurrent.atomic.AtomicInteger
import kotlin.math.abs
class Mp3Recorder(val context: Context) : Recorder {
val TAG = "Mp3Helper"
private var androidLame: AndroidLame? = null
private var mp3buffer: ByteArray = ByteArray(0)
private var isPaused = AtomicBoolean(false)
private var isStopped = AtomicBoolean(false)
private var outputPath: String? = null
private var fileDescriptor: FileDescriptor? = null
private var amplitude = AtomicInteger(0)
private val minBufferSize = AudioRecord.getMinBufferSize(
SAMPLE_RATE,
AudioFormat.CHANNEL_IN_MONO,
AudioFormat.ENCODING_PCM_16BIT
)
@SuppressLint("MissingPermission")
private val audioRecord = AudioRecord(
MediaRecorder.AudioSource.CAMCORDER,
SAMPLE_RATE,
AudioFormat.CHANNEL_IN_MONO,
AudioFormat.ENCODING_PCM_16BIT,
minBufferSize * 2
)
override fun setOutputFile(path: String) {
outputPath = path
}
override fun prepare() {}
override fun start() {
val data = ShortArray(minBufferSize)
mp3buffer = ByteArray((7200 + data.size * 2 * 1.25).toInt())
val outputStream: FileOutputStream = try {
if (fileDescriptor != null) {
FileOutputStream(fileDescriptor)
} else {
FileOutputStream(File(outputPath))
}
} catch (e: FileNotFoundException) {
e.printStackTrace()
return
}
val androidLame = LameBuilder()
.setInSampleRate(SAMPLE_RATE)
.setOutBitrate(context.config.bitrate / 1000)
.setOutSampleRate(SAMPLE_RATE)
.setOutChannels(1)
.build()
ensureBackgroundThread {
audioRecord.startRecording()
while (!isStopped.get()) {
if (!isPaused.get()) {
val count = audioRecord.read(data, 0, minBufferSize)
if (count > 0) {
val bytesEncoded: Int = androidLame.encode(data, data, count, mp3buffer)
if (bytesEncoded > 0) {
try {
updateAmplitude(data)
outputStream.write(mp3buffer, 0, bytesEncoded)
} catch (e: IOException) {
e.printStackTrace()
}
}
}
}
}
}
}
override fun stop() {
isPaused.set(true)
isStopped.set(true)
}
override fun pause() {
isPaused.set(true)
}
override fun resume() {
isPaused.set(false)
}
override fun release() {
androidLame?.flush(mp3buffer)
}
override fun getMaxAmplitude(): Int {
return amplitude.get()
}
override fun setOutputFile(fileDescriptor: FileDescriptor) {
this.fileDescriptor = fileDescriptor
}
private fun updateAmplitude(data: ShortArray) {
var sum = 0L
for (i in 0 until minBufferSize step 2) {
sum += abs(data[i].toInt())
}
amplitude.set((sum / (minBufferSize / 8)).toInt())
}
}

View File

@ -0,0 +1,57 @@
package com.simplemobiletools.voicerecorder.recorder
import android.annotation.SuppressLint
import android.content.Context
import android.media.MediaRecorder
import com.simplemobiletools.voicerecorder.extensions.config
import com.simplemobiletools.voicerecorder.helpers.SAMPLE_RATE
import java.io.FileDescriptor
class MediaRecorderWrapper(val context: Context) : Recorder {
private var recorder = MediaRecorder().apply {
setAudioSource(MediaRecorder.AudioSource.CAMCORDER)
setOutputFormat(context.config.getOutputFormat())
setAudioEncoder(context.config.getAudioEncoder())
setAudioEncodingBitRate(context.config.bitrate)
setAudioSamplingRate(SAMPLE_RATE)
}
override fun setOutputFile(path: String) {
recorder.setOutputFile(path)
}
override fun setOutputFile(fileDescriptor: FileDescriptor) {
recorder.setOutputFile(fileDescriptor)
}
override fun prepare() {
recorder.prepare()
}
override fun start() {
recorder.start()
}
override fun stop() {
recorder.stop()
}
@SuppressLint("NewApi")
override fun pause() {
recorder.pause()
}
@SuppressLint("NewApi")
override fun resume() {
recorder.resume()
}
override fun release() {
recorder.release()
}
override fun getMaxAmplitude(): Int {
return recorder.maxAmplitude
}
}

View File

@ -0,0 +1,15 @@
package com.simplemobiletools.voicerecorder.recorder
import java.io.FileDescriptor
interface Recorder {
fun setOutputFile(path: String)
fun setOutputFile(fileDescriptor: FileDescriptor)
fun prepare()
fun start()
fun stop()
fun pause()
fun resume()
fun release()
fun getMaxAmplitude(): Int
}

View File

@ -6,7 +6,6 @@ import android.app.*
import android.content.ContentValues
import android.content.Context
import android.content.Intent
import android.media.MediaRecorder
import android.media.MediaScannerConnection
import android.os.Build
import android.os.IBinder
@ -24,6 +23,8 @@ import com.simplemobiletools.voicerecorder.extensions.getDefaultRecordingsRelati
import com.simplemobiletools.voicerecorder.extensions.updateWidgets
import com.simplemobiletools.voicerecorder.helpers.*
import com.simplemobiletools.voicerecorder.models.Events
import com.simplemobiletools.voicerecorder.recorder.MediaRecorderWrapper
import com.simplemobiletools.voicerecorder.recorder.Recorder
import org.greenrobot.eventbus.EventBus
import java.io.File
import java.util.*
@ -40,7 +41,7 @@ class RecorderService : Service() {
private var status = RECORDING_STOPPED
private var durationTimer = Timer()
private var amplitudeTimer = Timer()
private var recorder: MediaRecorder? = null
private var recorder: Recorder? = null
override fun onBind(intent: Intent?): IBinder? = null
@ -86,40 +87,39 @@ class RecorderService : Service() {
currFilePath = "$baseFolder/${getCurrentFormattedDateTime()}.${config.getExtensionText()}"
try {
recorder = MediaRecorder().apply {
setAudioSource(MediaRecorder.AudioSource.CAMCORDER)
setOutputFormat(config.getOutputFormat())
setAudioEncoder(config.getAudioEncoder())
setAudioEncodingBitRate(config.bitrate)
setAudioSamplingRate(44100)
if (isRPlus() && hasProperStoredFirstParentUri(currFilePath)) {
val fileUri = createDocumentUriUsingFirstParentTreeUri(currFilePath)
createSAFFileSdk30(currFilePath)
val outputFileDescriptor = contentResolver.openFileDescriptor(fileUri, "w")!!.fileDescriptor
setOutputFile(outputFileDescriptor)
} else if (!isRPlus() && isPathOnSD(currFilePath)) {
var document = getDocumentFile(currFilePath.getParentPath())
document = document?.createFile("", currFilePath.getFilenameFromPath())
val outputFileDescriptor = contentResolver.openFileDescriptor(document!!.uri, "w")!!.fileDescriptor
setOutputFile(outputFileDescriptor)
} else {
setOutputFile(currFilePath)
}
prepare()
start()
duration = 0
status = RECORDING_RUNNING
broadcastRecorderInfo()
startForeground(RECORDER_RUNNING_NOTIF_ID, showNotification())
durationTimer = Timer()
durationTimer.scheduleAtFixedRate(getDurationUpdateTask(), 1000, 1000)
startAmplitudeUpdates()
recorder = if (recordMp3()) {
Mp3Recorder(this)
} else {
MediaRecorderWrapper(this)
}
if (isRPlus() && hasProperStoredFirstParentUri(currFilePath)) {
val fileUri = createDocumentUriUsingFirstParentTreeUri(currFilePath)
createSAFFileSdk30(currFilePath)
val outputFileDescriptor = contentResolver.openFileDescriptor(fileUri, "w")!!.fileDescriptor
recorder?.setOutputFile(outputFileDescriptor)
} else if (!isRPlus() && isPathOnSD(currFilePath)) {
var document = getDocumentFile(currFilePath.getParentPath())
document = document?.createFile("", currFilePath.getFilenameFromPath())
val outputFileDescriptor = contentResolver.openFileDescriptor(document!!.uri, "w")!!.fileDescriptor
recorder?.setOutputFile(outputFileDescriptor)
} else {
recorder?.setOutputFile(currFilePath)
}
recorder?.prepare()
recorder?.start()
duration = 0
status = RECORDING_RUNNING
broadcastRecorderInfo()
startForeground(RECORDER_RUNNING_NOTIF_ID, showNotification())
durationTimer = Timer()
durationTimer.scheduleAtFixedRate(getDurationUpdateTask(), 1000, 1000)
startAmplitudeUpdates()
} catch (e: Exception) {
showErrorToast(e)
stopRecording()
@ -137,7 +137,7 @@ class RecorderService : Service() {
release()
ensureBackgroundThread {
if (isRPlus() && !hasProperStoredFirstParentUri(currFilePath) ) {
if (isRPlus() && !hasProperStoredFirstParentUri(currFilePath)) {
addFileInNewMediaStore()
} else {
addFileInLegacyMediaStore()
@ -233,7 +233,7 @@ class RecorderService : Service() {
override fun run() {
if (recorder != null) {
try {
EventBus.getDefault().post(Events.RecordingAmplitude(recorder!!.maxAmplitude))
EventBus.getDefault().post(Events.RecordingAmplitude(recorder!!.getMaxAmplitude()))
} catch (ignored: Exception) {
}
}
@ -298,4 +298,8 @@ class RecorderService : Service() {
private fun broadcastStatus() {
EventBus.getDefault().post(Events.RecordingStatus(status))
}
private fun recordMp3(): Boolean {
return config.extension == EXTENSION_MP3
}
}