rnnoise icon indicating copy to clipboard operation
rnnoise copied to clipboard

Python - end to end instruction on using this library easily (also in notebook)

Open snakers4 opened this issue 5 years ago • 12 comments

Will post this instruction shortly

snakers4 avatar May 24 '19 08:05 snakers4

Step by step instructions

Python libraries

Use your package manager to install

pydub
numpy
scipy

Versions do not matter

Library installation

git clone [email protected]:xiph/rnnoise.git
cd rnnoise
./autogen.sh
./configure
make
make install

Code

import wave
import os,sys
import ctypes
import contextlib
import numpy as np
from ctypes import util
from scipy.io import wavfile
from pydub import AudioSegment

lib_path = util.find_library("rnnoise")
if (not("/" in lib_path)):
    lib_path = (os.popen('ldconfig -p | grep '+lib_path).read().split('\n')[0].strip().split(" ")[-1] or ("/usr/local/lib/"+lib_path))

lib = ctypes.cdll.LoadLibrary(lib_path)
lib.rnnoise_process_frame.argtypes = [ctypes.c_void_p,ctypes.POINTER(ctypes.c_float),ctypes.POINTER(ctypes.c_float)]
lib.rnnoise_process_frame.restype = ctypes.c_float
lib.rnnoise_create.restype = ctypes.c_void_p
lib.rnnoise_destroy.argtypes = [ctypes.c_void_p]

# borrowed from here 
# https://github.com/Shb742/rnnoise_python
class RNNoise(object):
    def __init__(self):
        self.obj = lib.rnnoise_create()
    def process_frame(self,inbuf):
        outbuf = np.ndarray((480,), 'h', inbuf).astype(ctypes.c_float)
        outbuf_ptr = outbuf.ctypes.data_as(ctypes.POINTER(ctypes.c_float))
        VodProb =  lib.rnnoise_process_frame(self.obj,outbuf_ptr,outbuf_ptr)
        return (VodProb,outbuf.astype(ctypes.c_short).tobytes())

    def destroy(self):
        lib.rnnoise_destroy(self.obj)


def read_wave(path):
    """Reads a .wav file.
    Takes the path, and returns (PCM audio data, sample rate).
    """
    with contextlib.closing(wave.open(path, 'rb')) as wf:
        num_channels = wf.getnchannels()
        assert num_channels == 1
        sample_width = wf.getsampwidth()
        assert sample_width == 2
        sample_rate = wf.getframerate()
        assert sample_rate in (8000, 16000, 32000, 48000)
        pcm_data = wf.readframes(wf.getnframes())
        return pcm_data, sample_rate        
        
      
def frame_generator(frame_duration_ms,
                    audio,
                    sample_rate):
    """Generates audio frames from PCM audio data.
    Takes the desired frame duration in milliseconds, the PCM data, and
    the sample rate.
    Yields Frames of the requested duration.
    """
    n = int(sample_rate * (frame_duration_ms / 1000.0) * 2)
    offset = 0
    timestamp = 0.0
    duration = (float(n) / sample_rate) / 2.0
    while offset + n < len(audio):
        yield audio[offset:offset + n]
        offset += n        
        
denoiser = RNNoise()      

Let's try it

wav_path = 'path_to_your_file.wav'


TARGET_SR = 48000
TEMP_FILE = 'test.wav'

sound = AudioSegment.from_wav(wav_path)
sound = sound.set_frame_rate(TARGET_SR)
sound = sound.set_channels(1)

sound.export(TEMP_FILE,
             format="wav")

audio, sample_rate = read_wave(TEMP_FILE)
assert sample_rate == TARGET_SR

frames = frame_generator(10, audio, TARGET_SR)
frames = list(frames)
tups = [denoiser.process_frame(frame) for frame in frames]
denoised_frames = [tup[1] for tup in tups]

denoised_wav = np.concatenate([np.frombuffer(frame,
                                             dtype=np.int16)
                               for frame in denoised_frames])

wavfile.write('test_denoised.wav',
              TARGET_SR,
              denoised_wav)

snakers4 avatar May 24 '19 08:05 snakers4

I was able to get this working after passing None into init of the RNNoise class as such:

class RNNoise(object): def __init__(self): self.obj = lib.rnnoise_create(None)

landonclark97 avatar Jun 04 '19 00:06 landonclark97

Is there a way with python (or matlab) to return the features?

Thanks!

ilor2 avatar Jun 14 '19 21:06 ilor2

@ilor2

Is there a way with python (or matlab) to return the features?

You need to import compute_frame_features function just like @snakers4 had with rnnoise_process_frame: https://github.com/xiph/rnnoise/blob/9acc1e5a633e0961a5895a73204df24744f199b6/src/denoise.c#L306

I don't know how to do it. But if you find a way post it here please.

p.s. @snakers4 btw thanks for open_stt!

slavaGanzin avatar Jun 17 '19 11:06 slavaGanzin

Thanks, I have a windows though and I don't think I can run autogen.sh on it. I'll post if I find a way around.

ilor2 avatar Jun 17 '19 14:06 ilor2

hey @snakers4 , the python script keeps crashing every time I try to run it! similar to what is mentioned in #50 , any idea how to fix that!?

ebadawy avatar Jul 17 '19 05:07 ebadawy

The example works as it should! I managed to inference an exported tflite model with a little bit of debugging. There is a way to separate the functionality into three parts (feature_extraction -> tflite_inference -> meta_process). The only difficult part is to properly allocate the numpys in Python, create c_type pointers and pass them by reference to the C functions.

The C functions looks like this :

Pre-process (feature extraction)

int rnnoise_extract_features(
  kiss_fft_cpx* X,
  kiss_fft_cpx* P,
  float *Ex,
  float *Ep,
  float *Exp,
  DenoiseState *st,
  float *features,
  const float *in) {

  float x[FRAME_SIZE];
  int silence;
  static const float a_hp[2] = {-1.99599, 0.99600};
  static const float b_hp[2] = {-2, 1};
  biquad(x, st->mem_hp_x, in, b_hp, a_hp, FRAME_SIZE);
  silence = compute_frame_features(st, X, P, Ex, Ep, Exp, features, x);

  return silence;
}

Meta-process

void rnnoise_meta_process(
  kiss_fft_cpx* X,
  kiss_fft_cpx* P,
  float *Ex,
  float *Ep,
  float *Exp,
  DenoiseState *st,
  float* g,
  float *out) {
  float gf[FREQ_SIZE]={1};

  pitch_filter(X, P, Ex, Ep, Exp, g);

  for (int i=0;i<NB_BANDS;i++) {
        float alpha = .6f;
        g[i] = MAX16(g[i], alpha*st->lastg[i]);
        st->lastg[i] = g[i];
    }
  interp_band_gain(gf, g);

  for (int i=0;i<FREQ_SIZE;i++) {
      X[i].r *= gf[i];
      X[i].i *= gf[i];
    }
  frame_synthesis(st, out, X);
}

kmonachopoulos avatar Aug 14 '19 08:08 kmonachopoulos

None

@landonclark97 I was able to get this working after passing None into init of the RNNoise class as such:

class RNNoise(object): def __init__(self): self.obj = lib.rnnoise_create(None)

hey can you help why none worked thxs

pranshurastogi29 avatar Nov 18 '19 17:11 pranshurastogi29

@pranshurastogi29 In the rnnoise_demo.c file provided you'll see that the rnnoise_create() function is called with NULL passed in as its only parameter. I'm guessing that something needs to be passed in when we call rnnoise_create() in our Python script, so in a way None can act as the Python equivalent of NULL - but that's just a guess.

landonclark97 avatar Nov 19 '19 00:11 landonclark97

hey @snakers4 , the python script keeps crashing every time I try to run it! similar to what is mentioned in #50 , any idea how to fix that!?

Hi! I had Segmentation fault when running this example, but during debugging I found out that adding print() before the denoiser initialization (denoiser = RNNoise()) fixes the problem. I've got no idea why it might happen, but maybe you'll manage to work around your problem with such trick

katyamineeva avatar Feb 19 '20 12:02 katyamineeva

You can do this on Linux. try running sudo apt-get install autoconf and you are good to go

vaishalibhardwaj avatar Jun 17 '20 12:06 vaishalibhardwaj

hey @snakers4 , the python script keeps crashing every time I try to run it! similar to what is mentioned in #50 , any idea how to fix that!?

Hi! I had Segmentation fault when running this example, but during debugging I found out that adding print() before the denoiser initialization (denoiser = RNNoise()) fixes the problem. I've got no idea why it might happen, but maybe you'll manage to work around your problem with such trick

However, just as you mentioned, adding print() before the denoiser initialization (denoiser = RNNoise()) doesn't work for me. Do you know what cause such segmentation fault, Thx.

LG-SS avatar May 10 '21 10:05 LG-SS