【问题标题】:How to find missing audio segments如何找到丢失的音频片段
【发布时间】:2015-11-03 06:44:06
【问题描述】:

我正在编写自己的音频格式作为游戏机项目的一部分。项目的一部分需要我编写一个模拟器,所以我确切地知道如何在硬件中实现它的功能。我目前正在编写 DSP 部分,但在编写解码算法时遇到了麻烦。在继续之前,我先解释一下我的格式。

DST(Dingo 音轨)音频格式

音频格式仅记录每个样本的数据片段:自上次样本以来的幅度和帧数。我会解释的。转换音频文件(例如 WAV)时,它会将当前样本与前一个样本进行比较。如果它检测到当前样本相对于前一个样本切换幅度方向,它会记录前一个样本和自上次记录以来的帧数。它一直持续到文件末尾。这是一个图表来进一步解释:

我需要做什么

我需要我的“DSP”来计算每个样本之间的数据,尽可能准确地仅使用给定的信息。我不认为这是我的编码算法,因为当我在 Audacity 中播放文件时,我可以辨认出原始歌曲。但是当我尝试用我的解码算法播放它时,我得到了分散的点击。我可以直接播放 WAV 文件,对算法进行一些修改,几乎没有质量下降,所以我知道这绝对是算法,而不是 DSP 的其余部分。

守则

所以现在我已经了解了所有基本信息,这是我的代码(仅重要部分)。

编码算法:

                FileInputStream s = null;
                BufferedWriter bw;
                    try {
                        int bytes;
                        int previous = 0;
                        int unsigned;
                        int frames = 0;
                        int size;
                        int cursor = 0;
                        boolean dir = true;
                        int bytes2;
                        int previous2 = 0;
                        int unsigned2;
                        int frames2 = 0;
                        boolean dir2 = true;
                        s = new FileInputStream(selectedFile);
                        size = (int)s.getChannel().size();
                        File f = new File(Directory.getPath() + "\\" + (selectedFile.getName().replace(".wav", ".dts")));
                        System.out.println(f.getPath());
                        if(!f.exists()){
                            f.createNewFile();
                        }
                        bw = new BufferedWriter(new FileWriter(f));
                        try (BufferedInputStream b = new BufferedInputStream(s)) {
                            byte[] data = new byte[128];
                            b.skip(44);
                            System.out.println("Loading...");
                            while ((bytes = b.read(data)) > 0) {
                              // do something
                              for(int i=1; i<bytes; i += 4) {
                                  unsigned = data[i] & 0xFF;
                                  if (dir) {
                                      if (unsigned < previous) {
                                          bw.write(previous);
                                          bw.write(frames);
                                          dir = !dir;
                                          frames = 0;
                                      }else{
                                          frames ++;
                                      }
                                  } else {
                                      if (unsigned > previous) {
                                          bw.write(previous);
                                          bw.write(frames);
                                          dir = !dir;
                                          frames = 0;
                                      }else{
                                          frames ++;
                                      }
                                  }
                                  previous = unsigned;
                                  cursor ++;
                                  unsigned2 = data[i + 2] & 0xFF;
                                  if (dir2) {
                                      if (unsigned2 < previous2) {
                                          bw.write(previous2);
                                          bw.write(frames2);
                                          dir2 = !dir2;
                                          frames2 = 0;
                                      }else{
                                          frames2 ++;
                                      }
                                  } else {
                                      if (unsigned2 > previous2) {
                                          bw.write(previous2);
                                          bw.write(frames2);
                                          dir2 = !dir2;
                                          frames2 = 0;
                                      }else{
                                          frames2 ++;
                                      }
                                  }
                                  previous2 = unsigned2;
                                  cursor ++;
                                  progress.setValue((int)(((float)(cursor / size)) * 100));
                              }
                            }
                            b.read(data);
                        }
                        bw.flush();
                        bw.close();
                        System.out.println("Done");
                        convert.setEnabled(true);
                        status.setText("finished");
                    } catch (Exception ex) {
                        status.setText("An error has occured");
                        ex.printStackTrace();
                        convert.setEnabled(true);
                }
                finally {
                    try {
                        s.close();
                    } catch (Exception ex) {
                        status.setText("An error has occured");
                        ex.printStackTrace();
                        convert.setEnabled(true);
                    }
                }

进度和状态对象可以忽略,因为它们是我的转换器工具 GUI 的一部分。此算法将 WAV 文件转换为我的格式 (DST)。

解码算法:

int start = bufferSize * (bufferNumber - 1);
short current;
short frames;
short count = 1;
short count2 = 1;
float jump;
for (int i = 0; i < bufferSize; i ++) {
    current = RAM.read(start + i);
    i++;
    frames = RAM.read(start + i);
    if (frames == 0) {
        buffer[count - 1] = current;
        count ++;
    } else {
        jump = current / frames;
        for (int i2 = 1; i2 < frames; i2++) {
            buffer[(2 * i2) - 1] = (short) (jump * i2);
            count ++;
        }
    }
    i++;
    current = RAM.read(start + i);
    i++;
    frames = RAM.read(start + i);
    if (frames == 0) {
        buffer[count2] = current;
        count2 ++;
    } else {
        jump = current / frames;
        for (int i2 = 1; i2 < frames; i2++) {
            buffer[2 * i2] = (short) (jump * i2);
            count2 ++;
        }
    }
}
bufferNumber ++;
if(bufferNumber > maxBuffer){
    bufferNumber = 1;
}

RAM 对象只是一个字节数组。 bufferNumber 和 maxBuffer 是指 DSP 内核使用的处理缓冲区的数量。缓冲区是生成的音频写入的对象。此算法集旨在转换立体声轨道,在我的格式中它的工作方式相同,但每个样本将包含两组数据,每个轨道一组。

问题

如何尽可能准确地找出每个样本之间丢失的音频,以及该方法的准确度如何?我很想简单地使用 WAV 格式,但我的控制台在内存 (RAM) 上是有限的。这种格式将处理音频所需的 RAM 空间减半。我还计划在 ARM 微控制器中实现这个算法,这将是控制台的真正 DSP。该算法也应该很快,但准确性更重要。如果我需要进一步澄清或解释任何事情,请告诉我,因为这是我的第一个大问题,我确信我忘记了一些东西。代码示例会很好,但不需要那么多。

编辑:

我设法让 DSP 输出一首歌曲,但它被加速并充满了静电。加速部分是由于它没有将轨道分成立体声(我认为)的一个小故障。静态是由于初始增量太陡。这是我得到的图片:

这是 DSP 中使用的新代码:

            if (frames == 0) {
                buffer[i - 1] = current;
                //System.out.println(current);
            } else {
                for (int i2 = 1; i2 < frames + 1; i2++) {
                    jump = (float)(previous + ((float)(current - previous) / (frames - i2 + 1)));
                    //System.out.println((short)jump);
                    buffer[(2 * i2) - 1] = (short)(jump);
                }
            }
            previous = current;

我需要一种方法来平滑这些初始增量,并且我不希望使用复杂的算术,因为当我将其移植到硬件时我的性能受到限制(最好是可以在 100MHZ ARM 控制器上运行同时能够保持 44.1KHZ 的采样率)。编辑:结果波实际上应该是向后的。对不起。

第二次编辑: 我让 DSP 以立体声输出,但不幸的是,这并没有像我希望的那样解决任何其他问题。我还修复了编码器的一些错误,所以现在它需要 8 位无符号音频。这已经成为一个数学问题,所以我想我会在数学堆栈交换中发布一个类似的问题。那是浪费时间。它很快就被搁置了。

【问题讨论】:

    标签: java algorithm audio


    【解决方案1】:

    您基本上已经记录了信号的局部极值,并且想要重建信号。最直接的方法是使用一些monotonic interpolation scheme。如果这符合您的需求,您可以尝试。但我想,结果会很不准确,因为信号的特征被忽略了。

    我不是音频工程师,所以我的假设可能是错误的。但也许,你有这些想法。

    信号基本上是正弦波的混合。为两个关键帧之间的任何段计算正弦函数非常容易。周期是他们距离的两倍。幅度由幅度差的一半给出。这将为您提供一个准确命中两个关键样本的正弦。此外,它会给你一个 C1 连续信号,因为连接点的导数为零。对于一个好的信号,你可能需要更多的平滑度。因此,您可以开始使用适当的窗口函数在关键帧周围插入两个正弦。我会从一个简单的三角形窗口开始,但其他的可能会给出更好的结果。此过程将保留极值。

    直观地解决这个问题(使用信号图)可能更容易,因此您可以看到结果。

    如果只考虑大小,那么您可能想研究已建立的音频压缩方法。它们通常提供比 1:2 更好的压缩比。另外,我不明白为什么这种方法会节省 RAM,因为您必须在解码时计算所有样本。当然,这假设不是将完整的数据加载到 RAM 中,而是分段流式传输。

    【讨论】:

    • 使用您提出的方法的问题是处理时间太长。而且我的格式节省了 RAM,因为我的控制台会将整个文件缓冲到系统内存中,然后将其中的大块流式传输到 DSP 以进行即时处理和输出。请参阅我的问题编辑。
    • 为什么要花很长时间?计算正弦参数很简单,可选的插值步骤也不难。
    • 那我试试看。但是如果它不能在 ARM 控制器上工作,我就不能使用它。让我们期待最好的。
    猜你喜欢
    • 2012-11-28
    • 2019-03-13
    • 2020-08-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-05-05
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多