pion / example-webrtc-applications

Examples of WebRTC applications that are large, or use 3rd party libraries
https://pion.ly/
MIT License
1.06k stars 248 forks source link

Receive audio with ffmpeg #108

Closed projetoarduino closed 3 years ago

projetoarduino commented 3 years ago

I'm trying to change the gstreamer-receive example to use ffmpeg, can someone tell me what I'm doing wrong because I don't hear any audio when I run the software

package main

import (
    "fmt"
    "time"
    "os"
    "os/exec"

    "github.com/pion/rtcp"
    "github.com/pion/webrtc/v3"
    //gst "gstreamer-sink"
)

func check(err error) {
    if err != nil {
        panic(err)
    }
}

func main() {
    // Prepare the configuration
    config := webrtc.Configuration{
        ICEServers: []webrtc.ICEServer{
            {
                URLs: []string{"stun:stun.l.google.com:19302"},
            },
        },
    }

    // Create a new RTCPeerConnection
    peerConnection, err := webrtc.NewPeerConnection(config)
    if err != nil {
        panic(err)
    }

    // Set a handler for when a new remote track starts, this handler creates a gstreamer pipeline
    // for the given codec
    peerConnection.OnTrack(func(track *webrtc.TrackRemote, receiver *webrtc.RTPReceiver) {

        // Send a PLI on an interval so that the publisher is pushing a keyframe every rtcpPLIInterval
        go func() {
            ticker := time.NewTicker(time.Second * 3)
            for range ticker.C {
                rtcpSendErr := peerConnection.WriteRTCP([]rtcp.Packet{&rtcp.PictureLossIndication{MediaSSRC: uint32(track.SSRC())}})
                if rtcpSendErr != nil {
                    fmt.Println(rtcpSendErr)
                }               
            }
        }()

        //codecName := strings.Split(track.Codec().RTPCodecCapability.MimeType, "/")[1]
        //fmt.Printf("Track has started, of type %d: %s \n", track.PayloadType(), codecName)
        //pipeline := gst.CreatePipeline(track.PayloadType(), strings.ToLower(codecName))
        //pipeline.Start()

        buf := make([]byte, 1400)
        chBuff := make(chan []byte, 1400)

        go playTrack(chBuff)

        for {
            i, _, readErr := track.Read(buf)
            if readErr != nil {
                panic(err)
            }
            chBuff <- buf[:i]
            //pipeline.Push(buf[:i])
            //fmt.Printf("%x", buf[:i])
            //fmt.Println(track.PayloadType())          
        }
    })

    // Set the handler for ICE connection state
    // This will notify you when the peer has connected/disconnected
    peerConnection.OnICEConnectionStateChange(func(connectionState webrtc.ICEConnectionState) {
        fmt.Printf("Connection State has changed %s \n", connectionState.String())
    })

    // Wait for the offer to be pasted
    offer := webrtc.SessionDescription{}
    Decode(MustReadStdin(), &offer)

    // Set the remote SessionDescription
    err = peerConnection.SetRemoteDescription(offer)
    if err != nil {
        panic(err)
    }

    // Create an answer
    answer, err := peerConnection.CreateAnswer(nil)
    if err != nil {
        panic(err)
    }

    // Create channel that is blocked until ICE Gathering is complete
    gatherComplete := webrtc.GatheringCompletePromise(peerConnection)

    // Sets the LocalDescription, and starts our UDP listeners
    err = peerConnection.SetLocalDescription(answer)
    if err != nil {
        panic(err)
    }

    // Block until ICE Gathering is complete, disabling trickle ICE
    // we do this because we only can exchange one signaling message
    // in a production application you should exchange ICE Candidates via OnICECandidate
    <-gatherComplete

    // Output the answer in base64 so we can paste it in browser
    fmt.Println(Encode(*peerConnection.LocalDescription()))

    // Block forever
    select {}
}

func playTrack(ch <-chan []byte){
    //cmd := exec.Command("ffmpeg", "-i", "pipe:0", "-f", "alsa", "default")
    cmd:= exec.Command("ffmpeg", "-i", "pipe:0", "-c:a", "copy", "-sample_fmt", "s16p", "-ssrc", "1", "-payload_type", "111",  "-b", "96k", "-f", "alsa", "default")

    cmd.Stderr = os.Stderr // bind log stream to stderr
    //cmd.Stdout = resultBuffer // stdout result will be written here

    stdin, err := cmd.StdinPipe() // Open stdin pipe
    check(err)

    err = cmd.Start() // Start a process on another goroutine
    check(err)

    for {
        _, err = stdin.Write(<-ch) // pump audio data to stdin pipe
        check(err)
    }

    err = stdin.Close() // close the stdin, or ffmpeg will wait forever
    check(err)

    err = cmd.Wait() // wait until ffmpeg finish
    check(err)
}
Sean-Der commented 3 years ago

Hey @projetoarduino

Instead of using stdin/stdout I would send RTP over loopback. See rtp-forwarder

If that example doesn't work for you please re-open or reach out via Slack thanks!

projetoarduino commented 3 years ago

Hi @Sean-Der

I would like to thank you for the quick response

I want to use stdin for the sake of practicality in my project, using loopback doesn't seem right

maybe if you could show me a way so i can continue my journey

Sean-Der commented 3 years ago

Transport is hard to do with audio/video over stdin unfortunately. You will have to put audio+video inside a container (webm) but that will add extra complexity.

I have done this a few times and loopback is the best way. It allows you to send multiple streams and is portable. You also aren't ever going to run out of ports. You can listen on anything in 127.0. 0.0 – 127.255.255.255