(Một bộ phim kinh điển mà mình cực kỳ thích nên lấy nó làm title cho bài viết này)
Đây là bản “hồi ký” khi mình tìm solution cho một bài toán, hi vọng nó sẽ giúp ai đó định hướng được đường phải đi.

Bài toán: mix 2 file audio trên android mà không dùng thêm library nào

  • Suy nghĩ đầu tiên là tìm xem android có chìa API nào ra để mix file ko – bỏ đi, các cậu ko cần search, android ko chìa api nào ra để làm việc này đâu
  • Suy nghĩ thứ 2: Đưa dữ liệu âm thanh(mp3..etc) về dạng raw data và thử mix các bit vào nhau? có vẻ khả thi

The bad:


private byte[] mBufData1 = null;
private byte[] mBufData2 = null;
private ArrayList mBufMixedData = new ArrayList<>();

private void loadMixedData() {
        int length1 = mBufData1.length;
        int length2 = mBufData2.length;
        int max = Math.max(length1, length2);
        int tempSplitSize;
        if (length1 == length2) {
            for (int i = 0; i < length1; i++) {
                mBufMixedData.add((byte) (mBufData1[i] + mBufData2[i]));
            }
        } else {
            if (length2 > length1) {
                tempSplitSize = length1;
            } else {
                tempSplitSize = length2;
            }
            for (int i = 0; i < tempSplitSize; i++) {
                mBufMixedData.add((byte) (mBufData1[i] + mBufData2[i]));
            }
            if (length2 > length1) {
                for (int i = tempSplitSize; i < max; i++) {
                    mBufMixedData.add((mBufData2[i]));
                }
            } else {
                for (int i = tempSplitSize; i < max; i++) {
                    mBufMixedData.add((mBufData1[i]));
                }
            }

        }
    }

Chạy được thật, âm thanh đã được mix lại như file karaoke ngoài hàng. Nhưng nhìn source code thì chỉ có đứa mù dở mới ko nhìn thấy vấn đề OOM chắc chắn phát sinh.
Nếu dừng ở đây – Ok, nó chạy được, chúng ta sẽ giấu đi đoạn OOM kia, kệ cho dự án hót shit vì còn lâu họ mới test ra issue ý, lúc phát hiện chúng ta đã cao chạy xa bay rồi. Ka ka ka
Chúng ta là những kẻ tồi tệ

The Ugly

Cải tiến hơn 1 chút, để tránh OOM, mình ko load hết dữ liệu lên ram nữa mà đưa vào DataOutputStream để write từng bit xuống file

private void createWaveMixing(String p1, String p2, String p3) throws IOException {
        int size1 = 0;
        int size2 = 0;
        int size1;
        int size2;

        FileInputStream fis1 = null;
        FileInputStream fis2 = null;
        try {
            fis1 = new FileInputStream(p1);
            fis2 = new FileInputStream(p2);
            size1 = fis1.available();
            size2 = fis2.available();
            long totalAudioLen = size1;
            if (size1 < size2) {
                totalAudioLen = size2;
            }
            long totalDataLen = totalAudioLen + WavUtil.LENGTH_EXTENDED;
            long longSampleRate = WavUtil.getSampleRate(p1);//44100
            long totalDataLen = totalAudioLen + WavUtils.LENGTH_EXTENDED;
            long longSampleRate = MediaCodecUtils.getSampleRate(p1);//44100
            int channels = 2;
            long byteRate = WavUtil.RECORDER_BPP * longSampleRate * channels / 8;
            long byteRate = WavUtils.RECORDER_BPP * longSampleRate * channels / 8;
            DataOutputStream out = null;
            try {
                out = new DataOutputStream(new FileOutputStream(p3));
                WavUtil.writeWaveFileHeader(out, totalAudioLen, totalDataLen, longSampleRate, channels, byteRate);
                WavUtils.writeWaveFileHeader(out, totalAudioLen, totalDataLen, longSampleRate, channels, byteRate);
                out.write(toByteArray(mBufMixedData));
            } catch (Exception e) {
                Log.e(TAG, "#createWaveMixing():" + e.getMessage(), e);
            } finally {
                if (out != null) {
                    out.close();
                }
            }
        } finally {
            if (fis1 != null) {
                fis1.close();
            }
            if (fis2 != null) {
                fis2.close();
            }
        }

    }

    private byte[] toByteArray(ArrayList in) {
        byte[] data = new byte[in.size()];
        for (int i = 0; i < data.length; i++) {
            data[i] = in.get(i);
        }
        return data;
    }
}

Cách làm này tránh được OOM, nhưng phải nói là nó chậm, thực sự chậm, chậm kinh khủng. Đâu đó mất khoảng 50 -60 s cho 2 file music raw dài 3 phút.
Nói chung là chạy được, ko có issue gì cả, chỉ chậm thôi. Chậm thì tự tìm cách mà improve đi, kêu gì – đúng ko các cậu? – lại chả phải quá.
Nếu dừng ở đây, chúng ta là những gã lừa đảo.

Nhưng dù sao mình cũng là người vừa đẹp trai lại tốt tính, nên mới xuất hiện tình huống thứ 3

The Good

Lần này mình optimize bằng cách sử dụng FileChannel, thay vì handle từng bit một, thì mình bóc một nhóm lớn ra để mix(buôn sỉ mới nhanh giầu)

private void createWaveMixing(String p1, String p2, String p3) throws IOException {
        int size1;
        int size2;

        FileInputStream fis1 = null;
        FileInputStream fis2 = null;
        try {
            fis1 = new FileInputStream(p1);
            fis2 = new FileInputStream(p2);
            size1 = fis1.available();
            size2 = fis2.available();
            long totalAudioLen = size1;
            if (size1 < size2) {
                totalAudioLen = size2;
            }
            long totalDataLen = totalAudioLen + WavUtils.LENGTH_EXTENDED;
            long longSampleRate = MediaExtractorUtils.getSampleRate(p1);//44100
            int channels = 2;
            long byteRate = WavUtils.RECORDER_BPP * longSampleRate * channels / 8;
            DataOutputStream out = null;

            FileChannel fc1 = fis1.getChannel();
            FileChannel fc2 = fis2.getChannel();
            long length1 = fc1.size();
            long length2 = fc2.size();
            try {
                out = new DataOutputStream(new FileOutputStream(p3));
                WavUtils.writeWaveFileHeader(out, totalAudioLen, totalDataLen, longSampleRate, channels, byteRate);
                {
                    ByteBuffer buff1 = ByteBuffer.allocate(BUFFER_SIZE);
                    ByteBuffer buff2 = ByteBuffer.allocate(BUFFER_SIZE);

                    ByteBuffer mixedBuffer = null;
                    if (length1 == length2) {
                        while (fc1.read(buff1) > 0) {
                            fc2.read(buff2);
                            mixedBuffer = mixByteBuffer(buff1, buff2);
                            out.write(mixedBuffer.array());
                            buff1.clear();
                            buff2.clear();
                            mixedBuffer.clear();
                        }
                        if (mixedBuffer != null) {
                            mixedBuffer.clear();
                        }
                    } else {
                        if (length2 > length1) {
                            while (fc1.read(buff1) > 0) {
                                fc2.read(buff2);
                                mixedBuffer = mixByteBuffer(buff1, buff2);
                                out.write(mixedBuffer.array());
                                buff1.clear();
                                buff2.clear();
                                mixedBuffer.clear();
                            }
                            while (fc2.read(buff2) > 0) {
                                out.write(buff2.array());
                                buff2.clear();
                            }
                        } else {
                            while (fc2.read(buff2) > 0) {
                                fc1.read(buff1);
                                mixedBuffer = mixByteBuffer(buff1, buff2);
                                out.write(mixedBuffer.array());
                                buff1.clear();
                                buff2.clear();
                                mixedBuffer.clear();
                            }
                            while (fc1.read(buff1) > 0) {
                                out.write(buff1.array());
                                buff1.clear();
                            }
                        }

                    }

                }
            } catch (Exception e) {
                Log.e("test", "#createWaveMixing():" + e.getMessage(), e);
            } finally {
                if (out != null) {
                    out.close();
                }
                fc1.close();
                fc2.close();
            }
        } finally {
            if (fis1 != null) {
                fis1.close();
            }
            if (fis2 != null) {
                fis2.close();
            }
        }

    }

Kết quả tốc độ tăng gấp 6-8 lần mà lại ko có issue gì cả
Lần này(dường như) mình sẽ có thể là người tốt

Cùng 1 bài toán, sẽ có các cách giải khác nhau
Khoảng thời gian cho phép khác nhau, chúng ta cũng sẽ phải chọn các giải pháp khác nhau(chấp nhận được, chạy có issue, chậm…etc)

1 ngày làm được ko? được – the bad
1 tuần làm được ko? được – the ugly
1 tháng làm được ko? được – the good

Cái gì cũng sẽ có giá của nó, tùy vào deadline, tùy vào thời điểm, chúng ta hãy cố chọn ra được cách làm tốt nhất

1 phút dành cho quảng cáo, GDK đã có 1 phiên bản có thể record, mix, nối audio nhé, anh em có thể search gdk-soundutilities

4 comments
0

4 comments

monz April 7, 2020 - 11:26 AM

Thực ra sếp có thể dùng MediaExtractor, MediaCodec và MediaMuxer để làm việc này một cách chính thống 😂

Reply
baka3k April 7, 2020 - 12:48 PM

MediaExtractor, MediaCodec và MediaMuxer thì nó lại là cả một câu truyện dài nên chưa dám kể cậu ạ

Reply
Phiêu Linh June 18, 2020 - 10:21 AM

Em vẫn ngồi đây đợi 2 save kể tiếp câu chuyện ạ :-“

Reply
Techover July 22, 2020 - 5:11 PM

Hóng

Reply

Leave a Comment

* By using this form you agree with the storage and handling of your data by this website.

You may also like

%d bloggers like this: