Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

how could I save audio to wavfile? #357

Open
Jackiexiao opened this issue Jul 30, 2021 · 14 comments
Open

how could I save audio to wavfile? #357

Jackiexiao opened this issue Jul 30, 2021 · 14 comments

Comments

@Jackiexiao
Copy link

Jackiexiao commented Jul 30, 2021

I want to use streamlit-webrtc to record wav and save to disk (like temp.wav'), but I have some problem, after I click stopbutton, audio_buffer becomeNone` instead of audio from microphone

my code

    webrtc_ctx = webrtc_streamer(
        key="sendonly-audio",
        mode=WebRtcMode.SENDONLY,
        audio_receiver_size=256,
        client_settings=WEBRTC_CLIENT_SETTINGS,
    )

    sound_window_len = 1  # 1ms

    audio_buffer = pydub.AudioSegment.silent(
        duration=sound_window_len
    )
    status_indicator = st.empty()

    while True:
        if webrtc_ctx.audio_receiver:
            try:
                audio_frames = webrtc_ctx.audio_receiver.get_frames(timeout=1)
            except queue.Empty:
                status_indicator.write("No frame arrived.")
                continue

            status_indicator.write("Running. Say something!")

            sound_chunk = pydub.AudioSegment.empty()
            for audio_frame in audio_frames:
                st.info('get audio frame')
                sound = pydub.AudioSegment(
                    data=audio_frame.to_ndarray().tobytes(),
                    sample_width=audio_frame.format.bytes,
                    frame_rate=audio_frame.sample_rate,
                    channels=len(audio_frame.layout.channels),
                )
                sound_chunk += sound

            if len(sound_chunk) > 0:
                audio_buffer += sound_chunk
        else:
            status_indicator.write("AudioReciver is not set. Abort.")
            break

    st.info("Writing wav to disk")
    audio_buffer.export('temp.wav', format='wav')
@whitphx
Copy link
Owner

whitphx commented Aug 1, 2021

Hi, please try below.

The reason is that your app script is re-executed every time component state (e.g. playing or not) changes then audio_buffer object differs in the execution where the audio recording loop is running and the next execution where .export() is called.
This re-execution is Streamlit's fundamental execution model (See https://docs.streamlit.io/en/stable/main_concepts.html#app-model),
and it provides some ways to handle problems like this case, and the example below uses st.session_state to keep the same sound object over executions.

import queue

import pydub

import streamlit as st
from streamlit_webrtc import webrtc_streamer, WebRtcMode, ClientSettings


def main():
    webrtc_ctx = webrtc_streamer(
        key="sendonly-audio",
        mode=WebRtcMode.SENDONLY,
        audio_receiver_size=256,
        client_settings=ClientSettings(
            rtc_configuration={
                "iceServers": [{"urls": ["stun:stun.l.google.com:19302"]}]
            },
            media_stream_constraints={
                "audio": True,
            },
        ),
    )

    if "audio_buffer" not in st.session_state:
        st.session_state["audio_buffer"] = pydub.AudioSegment.empty()

    status_indicator = st.empty()

    while True:
        if webrtc_ctx.audio_receiver:
            try:
                audio_frames = webrtc_ctx.audio_receiver.get_frames(timeout=1)
            except queue.Empty:
                status_indicator.write("No frame arrived.")
                continue

            status_indicator.write("Running. Say something!")

            sound_chunk = pydub.AudioSegment.empty()
            for audio_frame in audio_frames:
                sound = pydub.AudioSegment(
                    data=audio_frame.to_ndarray().tobytes(),
                    sample_width=audio_frame.format.bytes,
                    frame_rate=audio_frame.sample_rate,
                    channels=len(audio_frame.layout.channels),
                )
                sound_chunk += sound

            if len(sound_chunk) > 0:
                st.session_state["audio_buffer"] += sound_chunk
        else:
            status_indicator.write("AudioReciver is not set. Abort.")
            break

    audio_buffer = st.session_state["audio_buffer"]

    if not webrtc_ctx.state.playing and len(audio_buffer) > 0:
        st.info("Writing wav to disk")
        audio_buffer.export("temp.wav", format="wav")

        # Reset
        st.session_state["audio_buffer"] = pydub.AudioSegment.empty()


if __name__ == "__main__":
    main()

@whitphx
Copy link
Owner

whitphx commented Aug 1, 2021

If your purpose is only to record the sound to a file without any processing with pydub, recorder option is the simplest way:

from streamlit_webrtc import webrtc_streamer, WebRtcMode, ClientSettings
from aiortc.contrib.media import MediaRecorder


def main():
    def recorder_factory():
        return MediaRecorder("record.wav")

    webrtc_streamer(
        key="sendonly-audio",
        mode=WebRtcMode.SENDONLY,
        in_recorder_factory=recorder_factory,
        client_settings=ClientSettings(
            rtc_configuration={
                "iceServers": [{"urls": ["stun:stun.l.google.com:19302"]}]
            },
            media_stream_constraints={
                "audio": True,
                "video": False,
            },
        ),
    )


if __name__ == "__main__":
    main()

@Jackiexiao
Copy link
Author

thx very much for your help, I will dive into webRTC and streamlit for details, thxs

@Jackiexiao
Copy link
Author

If your purpose is only to record the sound to a file without any processing with pydub, recorder option is the simplest way:

from streamlit_webrtc import webrtc_streamer, WebRtcMode, ClientSettings
from aiortc.contrib.media import MediaRecorder


def main():
    def recorder_factory():
        return MediaRecorder("record.wav")

    webrtc_streamer(
        key="sendonly-audio",
        mode=WebRtcMode.SENDONLY,
        in_recorder_factory=recorder_factory,
        client_settings=ClientSettings(
            rtc_configuration={
                "iceServers": [{"urls": ["stun:stun.l.google.com:19302"]}]
            },
            media_stream_constraints={
                "audio": True,
                "video": False,
            },
        ),
    )


if __name__ == "__main__":
    main()

I use sample code as above, but it doesn't work as expected if you play it (seems it missing half of audio frame), it used to work before.

@Jackiexiao Jackiexiao reopened this Sep 6, 2021
@Jackiexiao
Copy link
Author

Jackiexiao commented Sep 6, 2021

if you put wavfile in audition, you get ( by the way, record wav using webrtc_ctx.audio_receiver.get_frames(timeout=1) works fine

@whitphx
image

@whitphx
Copy link
Owner

whitphx commented Sep 10, 2021

mm, while I'm not familiar with sound processing and cannot understand what the figures mean,
I wonder if setting some options to the MediaRecorder works.
MediaRecorder passes the options parameter to FFmpeg (via the underlying PyAV library). So, what if you set options like options={"sample_rate": ..., "channels": ...}?

@Jackiexiao
Copy link
Author

Jackiexiao commented Sep 23, 2021

set options doesn't work, but if I change mode to mode=WebRtcMode.SENDRECV instead of SENDONLY, MediaRecorder works fine

I write a gist here for reproduce

@hihunjin
Copy link

@whitphx @Jackiexiao I just did in your @Jackiexiao's gist. I played it right after I recorded it, and it seems the speed of the .wav file is two times faster. How could I solve this problem?

@Jackiexiao
Copy link
Author

@whitphx
Copy link
Owner

whitphx commented Sep 28, 2021

@Jackiexiao thank you for your investigation and problem-solving.
The fact that SENDRECV instead SENDONLY works fine is interesting hint. Thanks!

@JordiBustos
Copy link

JordiBustos commented Jan 2, 2022

Hi! Im using the code that you post there https://gist.github.com/Jackiexiao/9c45d5ad9caf524471b5ed966c57b5b9#file-streamlit_recorder-py-L131. When i click the START button it works just fine but when i stop it the audio file doesn't save it anywhere. I don't know if a need to change some parameter or something like that. Sorry im new coding with streamlit :)

@JordiBustos
Copy link

Actually im getting this log #552
Any idea on how to solve it :/?

@casiopa
Copy link

casiopa commented Jun 10, 2022

Hi @whitphx and @Jackiexiao, this code is very helpful. Thanks a lot!
I wonder how to, by default, turn off the speaker while recording. The goal is to prevent audio feedback when not wearing headphones.

HowToMuteAudioPlayer

@whitphx
Copy link
Owner

whitphx commented Oct 16, 2022

You can control the HTML attributes of the <audio /> element through the audio_html_attrs of webrtc_streamer() component as below.

    webrtc_ctx: WebRtcStreamerContext = webrtc_streamer(
        key="sendonly-audio",
        mode=WebRtcMode.SENDRECV,
        in_recorder_factory=recorder_factory,
        media_stream_constraints=MEDIA_STREAM_CONSTRAINTS,
        audio_html_attrs={"muted": True} # THIS!
    )

When you set only muted=true, the audio component is completely hidden. So if you want to show the audio control and mute the audio at the same time, set controls=true too as below.

        audio_html_attrs={"muted": True, "controls": True}

You can find other available attributes: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/audio

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants