@@ -222,7 +222,8 @@ static int writeOggPage(ogg_page *page, FILE *os) {
222222 return written ;
223223}
224224
225- const opus_int32 bitrate = OPUS_BITRATE_MAX ;
225+ const opus_int32 bitrate = 30 * 1024 ;
226+ static const int opus_application = OPUS_APPLICATION_AUDIO ;
226227const opus_int32 frame_size = 960 ;
227228const int with_cvbr = 1 ;
228229const int max_ogg_delay = 0 ;
@@ -343,7 +344,7 @@ int initRecorder(const char *path, opus_int32 sampleRate) {
343344 header .nb_streams = 1 ;
344345
345346 int result = OPUS_OK ;
346- _encoder = opus_encoder_create (coding_rate , 1 , OPUS_APPLICATION_VOIP , & result );
347+ _encoder = opus_encoder_create (coding_rate , 1 , opus_application , & result );
347348 if (result != OPUS_OK ) {
348349 LOGE ("Error cannot create encoder: %s" , opus_strerror (result ));
349350 return 0 ;
@@ -1315,7 +1316,7 @@ static AudioRecorderContext *createAudioRecorderContext(const char *path, opus_i
13151316 recorder -> encoder = opus_encoder_create (
13161317 recorder -> coding_rate ,
13171318 1 ,
1318- OPUS_APPLICATION_VOIP ,
1319+ opus_application ,
13191320 & result
13201321 );
13211322 if (result != OPUS_OK ) {
@@ -1437,10 +1438,13 @@ static AudioRecorderContext *createAudioRecorderContext(const char *path, opus_i
14371438 return recorder ;
14381439}
14391440
1440- static int writeAudioRecorderContextFrame (
1441+ // countedSamples is how many of the encoded samples belong to the stream duration; padding
1442+ // frames that only flush the encoder lookahead count zero.
1443+ static int writeAudioRecorderContextPacket (
14411444 AudioRecorderContext * recorder ,
14421445 uint8_t * framePcmBytes ,
14431446 uint32_t frameByteCount ,
1447+ opus_int32 countedSamples ,
14441448 int end
14451449) {
14461450 if (!recorder || !recorder -> encoder || !recorder -> file ) {
@@ -1450,7 +1454,7 @@ static int writeAudioRecorderContextFrame(
14501454 recorder -> packet_id ++ ;
14511455
14521456 opus_int32 nb_samples = frameByteCount / 2 ;
1453- recorder -> total_samples += nb_samples ;
1457+ recorder -> total_samples += countedSamples ;
14541458 recorder -> ogg_packet .e_o_s = end ;
14551459
14561460 int nbBytes = 0 ;
@@ -1549,6 +1553,63 @@ static int writeAudioRecorderContextFrame(
15491553 return 1 ;
15501554}
15511555
1556+ static int writeAudioRecorderContextFrame (
1557+ AudioRecorderContext * recorder ,
1558+ uint8_t * framePcmBytes ,
1559+ uint32_t frameByteCount ,
1560+ int end
1561+ ) {
1562+ return writeAudioRecorderContextPacket (
1563+ recorder ,
1564+ framePcmBytes ,
1565+ frameByteCount ,
1566+ (opus_int32 ) (frameByteCount / 2 ),
1567+ end
1568+ );
1569+ }
1570+
1571+ // Writes the buffered tail and enough silent frames to push the encoder lookahead out, then
1572+ // marks the last packet as end of stream. Its granule position trims the padding, so the
1573+ // stream ends exactly after the last captured sample.
1574+ static int finishAudioRecorderStream (AudioRecorderContext * recorder ) {
1575+ if (!recorder ) {
1576+ return 0 ;
1577+ }
1578+ if (recorder -> pending_samples > 0 ) {
1579+ int pendingSamples = recorder -> pending_samples ;
1580+ recorder -> pending_samples = 0 ;
1581+ if (!writeAudioRecorderContextFrame (
1582+ recorder ,
1583+ (uint8_t * ) recorder -> pending_frame ,
1584+ pendingSamples * sizeof (int16_t ),
1585+ 0
1586+ )) {
1587+ return 0 ;
1588+ }
1589+ }
1590+ ogg_int64_t endGranulePosition =
1591+ (recorder -> total_samples * 48000 + recorder -> rate - 1 ) / recorder -> rate
1592+ + recorder -> header .preskip ;
1593+ int16_t silence [960 ];
1594+ memset (silence , 0 , sizeof (silence ));
1595+ while (1 ) {
1596+ int end = recorder -> encoded_granule_position
1597+ + frame_size * 48000 / recorder -> coding_rate >= endGranulePosition ;
1598+ if (!writeAudioRecorderContextPacket (
1599+ recorder ,
1600+ (uint8_t * ) silence ,
1601+ sizeof (silence ),
1602+ 0 ,
1603+ end
1604+ )) {
1605+ return 0 ;
1606+ }
1607+ if (end ) {
1608+ return 1 ;
1609+ }
1610+ }
1611+ }
1612+
15521613static jlong flushAudioRecorderContext (AudioRecorderContext * recorder ) {
15531614 if (!recorder || !recorder -> file || fflush (recorder -> file ) != 0 ) {
15541615 return -1 ;
@@ -1631,15 +1692,17 @@ static int cropAudioRecorderOpusAudio(const char *inputPath, const char *outputP
16311692 }
16321693 remaining_samples -= samples_read ;
16331694
1634- int end = remaining_samples <= 0 ;
1635-
16361695 size_t byte_count = samples_read * sizeof (int16_t );
1637- if (!writeAudioRecorderContextFrame (recorder , (uint8_t * )buffer , byte_count , end )) {
1696+ if (!writeAudioRecorderContextFrame (recorder , (uint8_t * )buffer , byte_count , 0 )) {
16381697 LOGE ("Failed to write frame" );
16391698 success = 0 ;
16401699 break ;
16411700 }
16421701 }
1702+ if (success && !finishAudioRecorderStream (recorder )) {
1703+ LOGE ("Failed to finish cropped stream" );
1704+ success = 0 ;
1705+ }
16431706
16441707 free (buffer );
16451708 destroyAudioRecorderContext (recorder );
@@ -1648,9 +1711,13 @@ static int cropAudioRecorderOpusAudio(const char *inputPath, const char *outputP
16481711 return success ;
16491712}
16501713
1714+ // Re-encodes [startMs, endMs) of the prefix file into the recorder; a negative endMs keeps the
1715+ // rest of the file.
16511716static int appendRecorderPrefix (
16521717 AudioRecorderContext * recorder ,
1653- const char * prefixPath
1718+ const char * prefixPath ,
1719+ opus_int64 startMs ,
1720+ opus_int64 endMs
16541721) {
16551722 if (!recorder || !prefixPath ) {
16561723 return 0 ;
@@ -1673,6 +1740,26 @@ static int appendRecorderPrefix(
16731740 return 0 ;
16741741 }
16751742
1743+ opus_int64 remainingSamples = INT64_MAX ;
1744+ if (startMs > 0 || endMs >= 0 ) {
1745+ opus_int64 totalSamples = op_pcm_total (prefixFile , -1 );
1746+ if (totalSamples < 0 ) {
1747+ LOGE ("Failed to get Opus prefix length: %lld" , (long long ) totalSamples );
1748+ op_free (prefixFile );
1749+ return 0 ;
1750+ }
1751+ opus_int64 startSample = MAX (0 , MIN (totalSamples , startMs * 48000 / 1000 ));
1752+ opus_int64 endSample = endMs < 0
1753+ ? totalSamples
1754+ : MAX (startSample , MIN (totalSamples , endMs * 48000 / 1000 ));
1755+ if (startSample > 0 && op_pcm_seek (prefixFile , startSample ) != 0 ) {
1756+ LOGE ("Failed to seek Opus prefix to %lld" , (long long ) startSample );
1757+ op_free (prefixFile );
1758+ return 0 ;
1759+ }
1760+ remainingSamples = endSample - startSample ;
1761+ }
1762+
16761763 int16_t * buffer = malloc (sizeof (int16_t ) * frame_size );
16771764 if (!buffer ) {
16781765 LOGE ("Out of memory while rebuilding Opus prefix" );
@@ -1682,11 +1769,11 @@ static int appendRecorderPrefix(
16821769
16831770 int success = 1 ;
16841771 int bufferedSamples = 0 ;
1685- while (1 ) {
1772+ while (remainingSamples > 0 ) {
16861773 int samplesRead = op_read (
16871774 prefixFile ,
16881775 buffer + bufferedSamples ,
1689- frame_size - bufferedSamples ,
1776+ ( int ) MIN ( frame_size - bufferedSamples , remainingSamples ) ,
16901777 NULL
16911778 );
16921779 if (samplesRead < 0 ) {
@@ -1697,6 +1784,7 @@ static int appendRecorderPrefix(
16971784 if (samplesRead == 0 ) {
16981785 break ;
16991786 }
1787+ remainingSamples -= samplesRead ;
17001788 bufferedSamples += samplesRead ;
17011789 if (bufferedSamples == frame_size ) {
17021790 if (!writeAudioRecorderContextFrame (
@@ -1728,8 +1816,7 @@ static int appendRecorderPrefix(
17281816static int writeAudioRecorderFrame (
17291817 AudioRecorderContext * recorder ,
17301818 uint8_t * pcmBytes ,
1731- uint32_t byteCount ,
1732- int end
1819+ uint32_t byteCount
17331820) {
17341821 if (!recorder || (byteCount > 0 && !pcmBytes )) {
17351822 return 0 ;
@@ -1742,12 +1829,11 @@ static int writeAudioRecorderFrame(
17421829 while (sampleOffset < sampleCount ) {
17431830 if (recorder -> pending_samples == 0
17441831 && sampleCount - sampleOffset >= frame_size ) {
1745- int frameEnd = end && sampleOffset + frame_size == sampleCount ;
17461832 if (!writeAudioRecorderContextFrame (
17471833 recorder ,
17481834 (uint8_t * ) (samples + sampleOffset ),
17491835 frame_size * sizeof (int16_t ),
1750- frameEnd
1836+ 0
17511837 )) {
17521838 return 0 ;
17531839 }
@@ -1768,29 +1854,17 @@ static int writeAudioRecorderFrame(
17681854 sampleOffset += copySamples ;
17691855
17701856 if (recorder -> pending_samples == frame_size ) {
1771- int frameEnd = end && sampleOffset == sampleCount ;
17721857 if (!writeAudioRecorderContextFrame (
17731858 recorder ,
17741859 (uint8_t * ) recorder -> pending_frame ,
17751860 frame_size * sizeof (int16_t ),
1776- frameEnd
1861+ 0
17771862 )) {
17781863 return 0 ;
17791864 }
17801865 recorder -> pending_samples = 0 ;
17811866 }
17821867 }
1783-
1784- if (end && recorder -> pending_samples > 0 ) {
1785- int result = writeAudioRecorderContextFrame (
1786- recorder ,
1787- (uint8_t * ) recorder -> pending_frame ,
1788- recorder -> pending_samples * sizeof (int16_t ),
1789- 1
1790- );
1791- recorder -> pending_samples = 0 ;
1792- return result ;
1793- }
17941868 return 1 ;
17951869}
17961870
@@ -1799,7 +1873,9 @@ JNIEXPORT jlong Java_org_telegram_utils_recorder_audio_AudioRecorderNative_nCrea
17991873 jclass clazz ,
18001874 jstring path ,
18011875 jint sampleRate ,
1802- jstring prefixPath
1876+ jstring prefixPath ,
1877+ jlong prefixStartMs ,
1878+ jlong prefixEndMs
18031879) {
18041880 const char * pathString = (* env )-> GetStringUTFChars (env , path , 0 );
18051881 if (!pathString ) {
@@ -1818,7 +1894,12 @@ JNIEXPORT jlong Java_org_telegram_utils_recorder_audio_AudioRecorderNative_nCrea
18181894 destroyAudioRecorderContext (recorder );
18191895 return 0 ;
18201896 }
1821- int prefixResult = appendRecorderPrefix (recorder , prefixPathString );
1897+ int prefixResult = appendRecorderPrefix (
1898+ recorder ,
1899+ prefixPathString ,
1900+ prefixStartMs ,
1901+ prefixEndMs
1902+ );
18221903 (* env )-> ReleaseStringUTFChars (env , prefixPath , prefixPathString );
18231904 if (!prefixResult ) {
18241905 destroyAudioRecorderContext (recorder );
@@ -1842,12 +1923,8 @@ JNIEXPORT jint Java_org_telegram_utils_recorder_audio_AudioRecorderNative_nWrite
18421923 if (!recorder || !frameBytes || length < 0 || capacity < length ) {
18431924 return 0 ;
18441925 }
1845- return writeAudioRecorderFrame (
1846- recorder ,
1847- (uint8_t * ) frameBytes ,
1848- (uint32_t ) length ,
1849- length / 2 < frame_size
1850- );
1926+ // A short frame is just the tail of the capture; the stream ends only in nFinish.
1927+ return writeAudioRecorderFrame (recorder , (uint8_t * ) frameBytes , (uint32_t ) length );
18511928}
18521929
18531930JNIEXPORT jlong Java_org_telegram_utils_recorder_audio_AudioRecorderNative_nFlush (
@@ -1869,10 +1946,7 @@ JNIEXPORT jboolean Java_org_telegram_utils_recorder_audio_AudioRecorderNative_nF
18691946 return JNI_FALSE ;
18701947 }
18711948
1872- int result = 1 ;
1873- if (recorder -> pending_samples > 0 ) {
1874- result = writeAudioRecorderFrame (recorder , NULL , 0 , 1 );
1875- }
1949+ int result = finishAudioRecorderStream (recorder );
18761950 if (result && flushAudioRecorderContext (recorder ) < 0 ) {
18771951 result = 0 ;
18781952 }
0 commit comments