Android에서 PCM을 AMR로 변환하는 기법 및 문제 해결

PCM 데이터를 사용하는 음성 인식 서비스에서 반환된 원시 오디오를 AMR 형식으로 변환해야 하는 상황이 발생할 수 있습니다. 특히 알리바바의 음성 인식 기술을 활용할 경우, 출력은 일반적으로 16kHz의 PCM 포맷이며, 이를 8kHz AMR로 변환하기 위해서는 중간 단계가 필요합니다. 본문에서는 이를 위한 실용적인 변환 방식과 주의사항을 설명합니다.

전체 과정은 두 단계로 나뉩니다: 먼저 16kHz PCM을 8kHz로 리샘플링하여 정규화된 오디오 데이터를 생성한 후, 해당 데이터를 WAV 포맷으로 변환하고, 마지막으로 OpenCore-AMR 라이브러리를 이용해 AMR로 압축합니다.

첫 번째 단계에서는 16kHz PCM을 8kHz로 다운샘플링해야 합니다. 이 과정에서 잘못된 샘플링 비율을 사용하면 이후의 AMR 인코딩 결과가 왜곡되며, 음질 저하나 재생 불가 문제가 발생합니다. 이 작업은 JSSRC 라이브러리를 활용하며, 직접 소스 코드를 수정하여 16kHz → 8kHz 전용 리샘플러로 구성하였습니다.

다음은 리샘플링된 8kHz PCM을 WAV 파일로 변환하는 코드입니다:

public void pcmToWav(String inputPath, String outputPath, PcmToWavTransformListener listener) {
    final long sampleRate = 8000;
    final int channelCount = 1;
    final long byteRate = 8 * sampleRate * channelCount / 8;
    final int bufferSize = 1024;
    long totalAudioLen = 0;

    try (FileInputStream in = new FileInputStream(inputPath);
         FileOutputStream out = new FileOutputStream(outputPath)) {

        // 파일 크기 측정
        totalAudioLen = in.getChannel().size();

        // 리샘플러 초기화 (16kHz → 8kHz)
        JSSRCResampler resampler = new JSSRCResampler(in);

        // WAV 헤더 작성
        writeWaveHeader(out, totalAudioLen, sampleRate, channelCount, byteRate);

        byte[] buffer = new byte[bufferSize];
        int bytesRead;
        while ((bytesRead = resampler.read(buffer)) != -1) {
            out.write(buffer, 0, bytesRead);
            out.flush();
        }

        resampler.close();
        Log.d("AudioConverter", "WAV 변환 완료: " + outputPath);

        if (listener != null) {
            listener.onComplete(outputPath);
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
}

두 번째 단계에서는 변환된 8kHz WAV 파일을 AMR 형식으로 인코딩합니다. 이 과정에서는 OpenCore-AMR 라이브러리의 AmrEncoder 클래스를 사용합니다. 주의할 점은, WAV 파일의 16비트 리틀엔디안 데이터를 short[] 배열로 올바르게 해석해야 한다는 점입니다.

public void wavToAmr(String inputPath, String outputPath, TransformListener listener) {
    try (FileInputStream in = new FileInputStream(inputPath);
         FileOutputStream out = new FileOutputStream(outputPath)) {

        AmrEncoder.init(0);
        out.write(AMR_HEADER); // AMR 파일 헤더 삽입

        List<short[]> audioFrames = new ArrayList<>();
        byte[] buffer = new byte[320]; // 160개의 샘플에 대응하는 바이트 크기
        int readBytes;

        while ((readBytes = in.read(buffer)) > 0) {
            short[] frame = new short[160];
            ByteBuffer.wrap(buffer).order(ByteOrder.LITTLE_ENDIAN).asShortBuffer().get(frame);
            audioFrames.add(frame);
        }

        for (short[] frame : audioFrames) {
            byte[] encoded = new byte[32]; // 최대 인코딩 길이
            int encodedLength = AmrEncoder.encode(AmrEncoder.Mode.MR515.ordinal(), frame, encoded);
            if (encodedLength > 0) {
                out.write(encoded, 0, encodedLength);
            }
        }

        out.flush();
        Log.d("AudioConverter", "AMR 인코딩 완료: " + outputPath);

        if (listener != null) {
            listener.onComplete(outputPath);
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}

최종적으로 생성된 .amr 파일은 표준 8kHz AMR 포맷이며, 다양한 음성 처리 시스템에서 호환 가능합니다. 성능과 정확도를 위해 리샘플링 단계의 신뢰성은 매우 중요합니다.

참고 자료:

태그: Android PCM AMR audio conversion OpenCORE-AMR

8월 7일 11:57에 게시됨