harlanc / xiu

A simple,high performance and secure live media server in pure Rust (RTMP[cluster]/RTSP/WebRTC[whip/whep]/HTTP-FLV/HLS).🦀
https://www.rustxiu.com
MIT License
1.61k stars 168 forks source link

是否可以直接使用本库的RTMP协议推流? #35

Closed LuoZijun closed 1 year ago

LuoZijun commented 1 year ago

你好,我看到文档里面对于推流到RTMP服务器,建议采用通过 OBS 和 FFMPEG 这两种工具来做。但是我的推流过程当中可能有一些工作需要做,所以我是否可以直接通过本库的 RTMP 协议推流到一个远端的 RTMP 服务器呢?

我目前的使用场景是:对一个 HLS 流做一些处理后,然后推流给远端的 RTMP 服务器(B站),但是我对于 RTMP 协议不甚了解,不知作者能在代码 RTMP 推流这块能给一些 Example 吗?谢谢。

另外,我在 RTMP 协议代码里面的 ClientSession 里面,似乎也未曾看到 Auth 这块 -_-

harlanc commented 1 year ago

我理解你的这个场景需要把hls协议的数据转成rtmp协议的数据,然后推送到B站?现在还不支持hls到rtmp的转换。

Auth你指的是鉴权吗?正在开发中

LuoZijun commented 1 year ago

把hls协议的数据转成rtmp协议的数据,然后推送到B站?现在还不支持hls到rtmp的转换。

是的,将 HLS 流的数据,推送到 B站 的 RTMP 服务器。但是这个 HLS 到 RTMP 的转换,我可以自己来做。我自己有一些音视频处理方面的经验。我想,把 视频流音频流 的数据放进 RTMP 的 Message 里面,应该不是一件太困难的事情(对于常见的 Codec 而言 )。

Auth你指的是鉴权吗?

一些直播网站,会开放给你一个 RTMP 的地址(也可能还有其它的协议),同时有一些身份上面的数据。我想,推流到服务器时,该服务器会对这些身份信息做鉴定。比如 B站 的数据是这样的:

服务器地址: rtmp://live-push.bilivideo.com/live-bvc/
身份码:CCTTTT783****
串流密钥:?streamname=live_883*********&key=54********&schedule=rtmp&pflag=1

# `live-bvc` 就是 RTMP 里面的 `app_name`

但是如何把这个身份信息,通过 RTMP 库的 ClientSession 发送给 RTMP Server,我目前不太清楚。

如果有比较高层的 API ,我想应该容易很多,比如像这样:

const RTMP_DEFAULT_PORT: u16 = 1935u16;

struct RtmpClietConfig {
    app_name: String,
    stream_name: String,
    identity: String,
    secret: String,
}

impl Client {
    pub fn connect<A: IntoScoketAddr>(addr: A, config: RtmpClietConfig) -> Result<Self, std::io::Error> {
        let mut stream = std::net::TcpStream::connect(addr)?;
        rtmp_handshake(&mut stream, &config)?;
        Ok(Self { stream: stream, config: config })
    }
    pub fn publish(&mut self) -> Publisher;
    pub fn playback(&mut self) -> Playback;
}

struct Playback { }
struct Publisher { }

impl Publisher {
    // 写入 HLS 的 TS Packet,可能需要转换容器的格式。
    pub fn write_video_segment(&mut self, segment: Segment) -> Result<(), std::io::Error>;
    pub fn write_audio_segment(&mut self, segment: Segment) -> Result<(), std::io::Error>;
}

impl Playback {
    pub fn read_video_segment(&mut self, buf: &mut Segment) -> Result<(), std::io::Error>;
    pub fn read_audio_segment(&mut self, buf: &mut Segment) -> Result<(), std::io::Error>;
}
harlanc commented 1 year ago

@LuoZijun

我在dev分支写了一个pullrtmppushrtmp的例子,你看下。

你如果实现了HLS2RTMP的逻辑,RTMP library是通过rust channel把音视频数据发送到Transmitter的,用下面的enum封起来:

pub enum ChannelData {
    Video { timestamp: u32, data: BytesMut },
    Audio { timestamp: u32, data: BytesMut },
    MetaData { timestamp: u32, data: BytesMut },
}

我理解你的Auth的意思,就是使用本库的RTMP推流,需要推流地址携带鉴权信息发送到B站,才能推流成功,应该现在就支持,你用我给的demo试下吧

harlanc commented 1 year ago

测试出来一个拉第三方流,转推RTMP,拉不下来流的问题,RTMP协议信令发送应该有问题。

harlanc commented 1 year ago

bug修复了,完善了推拉流demo:

Usage: pprtmp --pull_rtmp_url <path> --push_rtmp_url <path>

Options:
  -i, --pull_rtmp_url <path>  Specify the pull rtmp url.
  -o, --push_rtmp_url <path>  Specify the push rtmp url.
  -h, --help                  Print help
  -V, --version               Print version

把抽象出API来作为SDK使用的想法很好,但是现在还做不到,但是client_session里面的逻辑我感觉比较清楚,如果推流的话,会订阅channels里面的音视频数据,然后发送出去:

   self.state = ClientSessionState::StartPublish;
                //subscribe from local session and publish to remote rtmp server
                if let (Some(app_name), Some(stream_name)) =
                    (&self.sub_app_name, &self.sub_stream_name)
                {
                    self.common
                        .subscribe_from_channels(
                            app_name.clone(),
                            stream_name.clone(),
                            self.session_id,
                        )
                        .await?;
                } else {
                    self.common
                        .subscribe_from_channels(
                            self.app_name.clone(),
                            self.stream_name.clone(),
                            self.session_id,
                        )
                        .await?;
                }

我觉得有两种做法,一种是把hls提取出来的音视频数据/metadata pushlish到channel里面,然后订阅,另一种是绕过channel,直接抽象出来API来写音视频数据/metadata,我有时间可以弄下,或者你也可以尝试弄一下^_^