首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >Aiortc简单无线电服务器(无双工音频通道)-解决方案

Aiortc简单无线电服务器(无双工音频通道)-解决方案
EN

Stack Overflow用户
提问于 2021-08-28 18:50:02
回答 1查看 943关注 0票数 1

档案:index.html

代码语言:javascript
复制
<html>
    <head>
        <meta charset="UTF-8"/>
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    
        <!-- jquery -->
        <script src="jquery-3.5.1.min.js"></script>
        
        <!-- own javascript -->
        <script src="telephone_calls.js"></script>
        
    </head>
    <body>
        <audio style="width:100%;margin-top:5mm;background:rgb(241,243,244)" id="audio" autoplay="true" controls="true"></audio>
    </body>
</html>

文件telephone_calls.js

代码语言:javascript
复制
// peer connection
var pc = null;

function createPeerConnection() {
    var config = {
        sdpSemantics: 'unified-plan',
        iceServers: [{urls: ['stun:stun.l.google.com:19302']}]
    };

    pc = new RTCPeerConnection(config);

    // connect audio
    pc.addEventListener('track', function(evt) {
        if (evt.track.kind == 'audio'){
            document.getElementById('audio').srcObject = evt.streams[0];
        };
    });

    return pc;
}

function negotiate() {
    return pc.createOffer().then(function(offer) {
        return pc.setLocalDescription(offer);
    }).then(function() {
        // wait for ICE gathering to complete
        return new Promise(function(resolve) {
            console.log(pc.iceGatheringState);
            if (pc.iceGatheringState === 'complete') {
                resolve();
            } else {
                function checkState() {
                    console.log(pc.iceGatheringState);
                    if (pc.iceGatheringState === 'complete') {
                        pc.removeEventListener('icegatheringstatechange', checkState);
                        resolve();
                    }
                }
                pc.addEventListener('icegatheringstatechange', checkState);

            }
        });
    }).then(function() {
        var offer = pc.localDescription;
        
        return fetch('/offer', {
            body: JSON.stringify({
                sdp: offer.sdp,
                type: offer.type
            }),
            headers: {
                'Content-Type': 'application/json'
            },
            method: 'POST'
        });
    }).then(function(response) {
        return response.json();
    }).then(function(answer) {
            return pc.setRemoteDescription(answer);
    }).catch(function(e) {
        alert(e);
        console.log(e);
    });
    
}


function start() {
    pc = createPeerConnection();
    
    dc = pc.createDataChannel('radio_metadata', {"ordered": true});
    dc.onclose = function() {
        
    };
    dc.onopen = function() {
        
    };
    dc.onmessage = function(evt) {
        console.log(evt.data);
    };
    
    //negotiate();
    
    constraints = {audio:true,video:false};
    
    navigator.mediaDevices.getUserMedia(constraints).then(function(stream) {
        stream.getTracks().forEach(function(track) {
            pc.addTrack(track, stream);
        });
        return negotiate();
        }, function(err) {
            alert('Could not acquire media: ' + err);
    });
    
    
}

$(document).ready(function(){
    start();        
})

档案:server.py

代码语言:javascript
复制
from aiohttp import web
from aiortc.mediastreams import MediaStreamTrack
from aiortc import RTCPeerConnection, RTCSessionDescription
from aiortc.contrib.media import MediaPlayer
from pydub import AudioSegment
import av
import pyaudio
import asyncio
import json
import os
from multiprocessing import Process, freeze_support
from queue import Queue
import sys
import threading
from time import sleep
import fractions
import time

class RadioServer(Process):
    def __init__(self,q):
        super().__init__()
        self.q = q
        self.ROOT = os.path.dirname(__file__)
        self.pcs = []
        self.channels = []
        self.stream_offers = []
    
    def run(self):
        self.app = web.Application()
        self.app.on_shutdown.append(self.on_shutdown)
        self.app.router.add_get("/", self.index)
        self.app.router.add_get("/telephone_calls.js", self.javascript)
        self.app.router.add_get("/jquery-3.5.1.min.js", self.jquery)
        self.app.router.add_post("/offer", self.offer)
        
        threading.Thread(target=self.fill_the_queues).start()
        web.run_app(self.app, access_log=None, host="192.168.1.20", port="8080", ssl_context=None)

    def fill_the_queues(self):
        while(True):
            frame = self.q.get()
            for stream_offer in self.stream_offers:
                stream_offer.q.put(frame)

    async def index(self,request):
        content = open(os.path.join(self.ROOT, "index.html"), encoding="utf8").read()
        return web.Response(content_type="text/html", text=content)


    async def javascript(self,request):
        content = open(os.path.join(self.ROOT, "telephone_calls.js"), encoding="utf8").read()
        return web.Response(content_type="application/javascript", text=content)

    async def jquery(self,request):
        content = open(os.path.join(self.ROOT, "jquery-3.5.1.min.js"), encoding="utf8").read()
        return web.Response(content_type="application/javascript", text=content)

    async def offer(self,request):  
        params = await request.json()
        offer = RTCSessionDescription(sdp=params["sdp"], type=params["type"])

        pc = RTCPeerConnection()
        self.pcs.append(pc)

        # prepare epalxeis media
        self.stream_offers.append(CustomRadioStream())
        pc.addTrack(self.stream_offers[-1])

        #player = MediaPlayer(os.path.join(self.ROOT, "ΑΓΙΑ ΚΥΡΙΑΚΗ.mp3"))
        #pc.addTrack(player.audio)

        @pc.on("datachannel")
        def on_datachannel(channel):
            self.channels.append(channel)
            self.send_channel_message(str(len(self.pcs)))


        @pc.on("iceconnectionstatechange")
        async def on_iceconnectionstatechange():
            if pc.iceConnectionState == "failed":
                self.pcs.remove(pc)
                print("Current peer connections:"+str(len(self.pcs)))
            

        # handle offer
        await pc.setRemoteDescription(offer)

        # send answer
        answer = await pc.createAnswer()
        await pc.setLocalDescription(answer)
        
            

        return web.Response(content_type="application/json",text=json.dumps({"sdp": pc.localDescription.sdp, "type": pc.localDescription.type}))

    async def on_shutdown(self,app):
        # close peer connections
        if self.pcs:
            coros = [pc.close() for pc in self.pcs]
            await asyncio.gather(*coros)
            self.pcs = []
            self.channels = []
            self.stream_offers = []
            
    def send_channel_message(self,message):
        for channel in self.channels:
            channel.send(message)
            

class CustomRadioStream(MediaStreamTrack):
    kind = "audio"
    
    def __init__(self):
        super().__init__()  # don't forget this!
        
        self.q = Queue()
        self._start = None
        
    async def recv(self):
        frame = self.q.get()
        frame_time = frame.time
        if self._start is None:
            self._start = time.time() - frame_time
        else:
            wait = self._start + frame_time - time.time()
            await asyncio.sleep(wait)
        return frame

class RadioOutputStream:
    def __init__(self,q):
        self.sample_rate = 44800
        self.AUDIO_PTIME = 0.744
        self.samples = int(self.AUDIO_PTIME * self.sample_rate)
        self.packet_time = 20

        self.FORMAT = pyaudio.paInt16
        self.CHANNELS = 2
        self.RATE = self.sample_rate
        self.CHUNK = int(44100*0.744)

        self.file_segment = AudioSegment.from_file(r"ΑΓΙΑ ΚΥΡΙΑΚΗ.mp3").set_frame_rate(self.sample_rate)
        self.duration_milliseconds = len(self.file_segment)
        self.chunk_number = 0

        self.silence = AudioSegment.silent(duration=self.packet_time)

        self.q = q

        self.codec = av.CodecContext.create('pcm_s16le', 'r')
        self.codec.sample_rate = 44800
        self.codec.channels = 2

        self.audio_samples = 0
        
    def run_stream(self):
        while(True):
            time_start = time.time()
            if((self.chunk_number+1)*(self.packet_time)<=self.duration_milliseconds):
                final_slice = self.file_segment[self.chunk_number*self.packet_time:(self.chunk_number+1)*self.packet_time]
                #final_slice = final_slice + 100
                self.chunk_number += 1

                packet = av.Packet(final_slice.raw_data)
                frame = self.codec.decode(packet)[0]
                frame.pts = self.audio_samples
                frame.time_base = fractions.Fraction(1, self.codec.sample_rate)
                self.audio_samples += frame.samples
                self.q.put(frame)
            else:
                if(self.chunk_number*self.packet_time<self.duration_milliseconds):
                    final_slice = self.file_segment[self.chunk_number*self.packet_time:]
                    final_slice = final_slice + self.silence
                    #final_slice = final_slice + 100
                    self.chunk_number += 1


                    packet = av.Packet(final_slice.raw_data)
                    frame = self.codec.decode(packet)[0]
                    frame.pts = self.audio_samples
                    frame.time_base = fractions.Fraction(1, self.codec.sample_rate)
                    self.audio_samples += frame.samples
                    self.q.put(frame)
                else:
                    #start song from begin
                    self.chunk_number=0
            
            #time_end = time.time()
            #sleep_time = time_end-time_start
            #if sleep_time>0.020:
            #   sleep(sleep_time-0.020)
            sleep(0.01)

if __name__ == "__main__":
    freeze_support()
    
    q = Queue()
    radio_stream = RadioOutputStream(q)
    threading.Thread(target=radio_stream.run_stream).start()

    custom_server_child_process = RadioServer(q)
    custom_server_child_process.run()

我想改变这一点:

代码语言:javascript
复制
    constraints = {audio:true,video:false};
    
    navigator.mediaDevices.getUserMedia(constraints).then(function(stream) {
        stream.getTracks().forEach(function(track) {
            pc.addTrack(track, stream);
        });
        return negotiate();
        }, function(err) {
            alert('Could not acquire media: ' + err);
    });

对此:

代码语言:javascript
复制
negotiate();

但是,如果我这样做,服务器端将出现以下错误:

代码语言:javascript
复制
C:\Users\Χρήστος\Desktop\radio>python server.py
======== Running on http://192.168.1.20:8080 ========
(Press CTRL+C to quit)
Error handling request
Traceback (most recent call last):
  File "C:\Python\lib\site-packages\aiohttp\web_protocol.py", line 422, in _handle_request
    resp = await self._request_handler(request)
  File "C:\Python\lib\site-packages\aiohttp\web_app.py", line 499, in _handle
    resp = await handler(request)
  File "C:\Users\Χρήστος\Desktop\radio\server.py", line 90, in offer
    await pc.setLocalDescription(answer)
  File "C:\Python\lib\site-packages\aiortc\rtcpeerconnection.py", line 768, in setLocalDescription
    t._currentDirection = and_direction(t.direction, t._offerDirection)
  File "C:\Python\lib\site-packages\aiortc\rtcpeerconnection.py", line 247, in and_direction
    return sdp.DIRECTIONS[sdp.DIRECTIONS.index(a) & sdp.DIRECTIONS.index(b)]
ValueError: None is not in list

有人能帮我创造一个空的报价吗?

EN

回答 1

Stack Overflow用户

发布于 2021-08-30 16:09:29

我更改了协商javascript函数。现在是:

代码语言:javascript
复制
function negotiate() {
    return pc.createOffer({offerToReceiveAudio:true}).then(function(offer) {
        return pc.setLocalDescription(offer);
    }).then(function() {
        // wait for ICE gathering to complete
        return new Promise(function(resolve) {
            console.log(pc.iceGatheringState);
            if (pc.iceGatheringState === 'complete') {
                resolve();
            } else {
                function checkState() {
                    console.log(pc.iceGatheringState);
                    if (pc.iceGatheringState === 'complete') {
                        pc.removeEventListener('icegatheringstatechange', checkState);
                        resolve();
                    }
                }
                pc.addEventListener('icegatheringstatechange', checkState);

            }
        });
    }).then(function() {
        var offer = pc.localDescription;
        
        return fetch('/offer', {
            body: JSON.stringify({
                sdp: offer.sdp,
                type: offer.type
            }),
            headers: {
                'Content-Type': 'application/json'
            },
            method: 'POST'
        });
    }).then(function(response) {
        return response.json();
    }).then(function(answer) {
            return pc.setRemoteDescription(answer);
    }).catch(function(e) {
        alert(e);
        console.log(e);
    });
    
}

唯一的变化是:

代码语言:javascript
复制
pc.createOffer()

-->

代码语言:javascript
复制
pc.createOffer({offerToReceiveAudio:true})

进一步研究的链接

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/68967311

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档