Recently Written · git

subread-anki

One tap makes an Anki card on Android: word, definition, sentence, screenshot, audio. Works with any app, with SubRead Dictionary and SubRead Overlay.

git clone https://github.com/equwal/subread-anki

Log | Files | Refs


app/src/main/kotlin/space/subread/anki/AacEncoder.kt (3634 bytes)

1 package space.subread.anki
2 
3 import android.media.MediaCodec
4 import android.media.MediaCodecInfo
5 import android.media.MediaFormat
6 import android.media.MediaMuxer
7 import java.io.File
8 import java.nio.ByteOrder
9 
10 /**
11  * Writes 16-bit PCM as AAC in an `.m4a`. AAC because every player has it, Anki on iOS
12  * included, and because Android has the encoder on each device since API 16.
13  */
14 object AacEncoder {
15 
16     private const val MIME = MediaFormat.MIMETYPE_AUDIO_AAC
17     private const val TIMEOUT_US = 10_000L
18 
19     /** Encodes [pcm], interleaved with [channels] channels at [sampleRate], into [out]. */
20     fun encode(pcm: ShortArray, sampleRate: Int, channels: Int, out: File) {
21         require(pcm.isNotEmpty()) { "no sound to encode" }
22         val format = MediaFormat.createAudioFormat(MIME, sampleRate, channels).apply {
23             setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC)
24             setInteger(MediaFormat.KEY_BIT_RATE, 64_000 * channels)
25             setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, 64 * 1024)
26         }
27         val codec = MediaCodec.createEncoderByType(MIME)
28         codec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE)
29         codec.start()
30         val muxer = MediaMuxer(out.path, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4)
31         var track = -1
32         var fed = 0
33         var inputDone = false
34         var outputDone = false
35         val info = MediaCodec.BufferInfo()
36         try {
37             while (!outputDone) {
38                 if (!inputDone) {
39                     val index = codec.dequeueInputBuffer(TIMEOUT_US)
40                     if (index >= 0) {
41                         val buffer = codec.getInputBuffer(index)!!
42                         buffer.clear()
43                         // A frame must stay whole: the count is a multiple of the channels.
44                         val count = minOf(buffer.capacity() / 2, pcm.size - fed) / channels * channels
45                         buffer.order(ByteOrder.LITTLE_ENDIAN).asShortBuffer().put(pcm, fed, count)
46                         val timeUs = fed / channels * 1_000_000L / sampleRate
47                         fed += count
48                         val flags = if (fed >= pcm.size) MediaCodec.BUFFER_FLAG_END_OF_STREAM else 0
49                         if (flags != 0) inputDone = true
50                         codec.queueInputBuffer(index, 0, count * 2, timeUs, flags)
51                     }
52                 }
53                 val index = codec.dequeueOutputBuffer(info, TIMEOUT_US)
54                 when {
55                     index == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED -> {
56                         track = muxer.addTrack(codec.outputFormat)
57                         muxer.start()
58                     }
59                     index >= 0 -> {
60                         val buffer = codec.getOutputBuffer(index)!!
61                         val config = info.flags and MediaCodec.BUFFER_FLAG_CODEC_CONFIG != 0
62                         if (!config && info.size > 0 && track >= 0) {
63                             buffer.position(info.offset)
64                             buffer.limit(info.offset + info.size)
65                             muxer.writeSampleData(track, buffer, info)
66                         }
67                         codec.releaseOutputBuffer(index, false)
68                         if (info.flags and MediaCodec.BUFFER_FLAG_END_OF_STREAM != 0) outputDone = true
69                     }
70                 }
71             }
72         } finally {
73             runCatching { codec.stop() }
74             codec.release()
75             runCatching { if (track >= 0) muxer.stop() }
76             muxer.release()
77         }
78     }
79 }