简体   繁体   English

使用HTTP进行实时音频流传输-选择协议和Java实现

[英]Real-time audio streaming using HTTP - choosing the protocol and Java implementation

I'm trying to implement simple HTTP server for real-time audio (in Java). 我正在尝试为实时音频(在Java中)实现简单的HTTP服务器。 Suppose there is a website where you can see a list of songs which are playing one after another. 假设有一个网站,您可以在其中看到一首又一首播放的歌曲列表。 When client connects to server - lets say in the middle of the song - I'm thinking to use "Range" HTTP header and send the data range starting from that part of song. 当客户端连接到服务器时-在歌曲的中间说-我正在考虑使用“ Range” HTTP标头,并从歌曲的该部分开始发送数据范围。 But if during download connection is temporary lost (and song finished) - should the server send previous song part and finish it - or should the server send those parts of song which is playing at that moment? 但是,如果在下载过程中连接暂时丢失(歌曲已完成)-服务器应该发送先前的歌曲部分并完成它-还是服务器应该发送当时正在播放的那些歌曲部分? What are best practices/principles? 什么是最佳做法/原则?

PS - I'm not looking for 3rd party software for audio streaming. PS-我不是在寻找音频流的第三方软件。

EDIT: 编辑:
Now after some research in available real-time streaming technologies, I see these goals: 现在,在对可用的实时流技术进行了一些研究之后,我看到了以下目标:
1. Choosing protocol for simple real-time audio streaming 1.为简单的实时音频流选择协议
2. Protocol implementation in Java (server side) 2. Java中的协议实现(服务器端)

You cannot arbitrarily cut media and expect a player to be able to play it. 您不能随意剪切媒体并期望播放器能够播放它。 This works with bare MPEG streams, but other containers and codecs can have trouble. 这适用于裸露的MPEG流,但是其他容器和编解码器可能会遇到麻烦。 Because of this don't send partial files unless the client already has the rest of it. 因此,除非客户端已经拥有其余文件,否则不要发送部分文件。

You also have the problem of what to do when the song ends and you're on to the next. 您还遇到了当歌曲结束并转到下一首时该怎么办的问题。

There are two ways to implement this. 有两种方法可以实现此目的。 One of which is to have static media available to your clients and then seek to the correct time in the audio client-side. 一种方法是让客户端可以使用静态媒体,然后在音频客户端寻求正确的时间。

The way I would choose is to truly create an internet radio stream where everyone hears the same thing at the same time because you effectively have a common buffer which chunks are copied from and sent to all clients about the same time. 我选择的方式是真正创建一个互联网广播流,使每个人都在同一时间听到同一件事,因为您实际上拥有一个公共缓冲区,该缓冲区大约在同一时间从所有客户端复制并发送给所有客户端。 Now, if you do this you will either need to use codecs/containers that support arbitrary splicing (MP3 or AAC) or re-wrap streams with the container as they are sent to the client. 现在,如果您执行此操作,则将需要使用支持任意拼接(MP3或AAC)的编解码器/容器,或者在将流发送到客户端时用容器重新包装流。 This is a complicated problem, so it's best if you use something off-the-shelf that does this, like Icecast. 这是一个复杂的问题,所以最好使用像Icecast这样的现成的东西来做。 I know you say you're not looking for third-party solutions, but that's the best way. 我知道您说您不是在寻找第三方解决方案,但这是最好的方法。 If you want to do it all yourself, you'll have to reimplement all of it, or support MPEG streams only. 如果您想自己完成所有操作,则必须重新实现所有功能,或者仅支持MPEG流。

EDIT: From your comment: 编辑:从您的评论:

Could you explain more about data stream format, which is [24,576 bytes of stream] [metablock] [24,576 bytes of stream] [metablock] etc.. How to separate blocks, and what are the contents of metablocks? 您能否解释一下有关数据流格式的更多信息,即[24,576字节的流] [元块] [24,576字节的流] [元块]等。如何分离块,元块的内容是什么?

If you wish, you can mux SHOUTcast-style metadata into your stream. 如果愿意,可以将SHOUTcast样式的元数据混入流中。 Not all clients support this. 并非所有客户都支持。 If they do, they will send you the following header in the request: 如果这样做,他们将在请求中向您发送以下标头:

Icy-MetaData: 1

If you see that header and value, you can optionally include metadata in the stream. 如果看到该标头和值,则可以选择在流中包括元数据。 The metadata is simply injected after every chunk of stream data. 元数据仅在流数据的每个块之后注入。 To include metadata, first you need to decide how big your stream chunks are. 要包含元数据,首先需要确定流块的大小。 Too far apart, and the metadata won't align well to the stream. 相距太远,元数据无法与流很好地对齐。 Too close together and in theory you are wasting bandwidth (but not much, since an unchanging metadata block is only a byte long). 两者之间的距离太近,从理论上讲您正在浪费带宽(但不会浪费太多,因为不变的元数据块只有一个字节长)。 I usually stick with 8KB. 我通常坚持使用8KB。 It's not uncommon to see 16KB and sometimes 32KB. 看到16KB有时是32KB的情况并不少见。 Output that chunk size, the metadata interval, in the response headers: 在响应头中输出该块大小(元数据间隔):

Icy-MetaInt: 8192

To get things started, send 8192 bytes (8KB) of audio stream data to the client. 首先,将8192字节(8KB)的音频流数据发送到客户端。

Now it's time for a metadata block. 现在是时候使用元数据块了。 Start with a string, like this: 以字符串开头,如下所示:

StreamTitle='This is my stream title';StreamUrl='';

You can pass in StreamUrl or even other fields, but only StreamTitle is really used by clients these days. 您可以传入StreamUrl甚至其他字段,但是这些天客户实际上只使用StreamTitle。 ( StreamUrl used to have the ability to popup a browser by capitalizing some letters or something, I don't remember for certainty what the trigger was. It's no longer used.) Then convert this string to a buffer and pad with null bytes ( 0x00 ) to the nearest evenly divisible block of 16. That is, if the string version of your metadata block is 51 bytes long, you need it to be 64 bytes long so you will add 13 bytes of NUL padding. StreamUrl曾经能够通过大写一些字母或某物来弹出浏览器,我不敢肯定会记得触发器是什么。不再使用它。)然后将此字符串转换为缓冲区并填充空字节( 0x00 )到最接近的等分的16块。也就是说,如果元数据块的字符串版本长51字节,则您需要将其长64字节,因此将添加13字节的NUL填充。

A quick note on character set. 关于字符集的快速注释。 Many clients support UTF-8 in their metadata. 许多客户端在其元数据中支持UTF-8。 Some don't. 有些没有。 Also, if you have to use an apostrophe ' in your metadata, it needs to be escaped. 另外,如果必须在元数据中使用撇号' ,则需要对其进行转义。 Unfortunately there doesn't seem to be a truly standard way of doing this. 不幸的是,似乎没有真正的标准方法来做到这一点。 Backslash sometimes works. 反斜杠有时会起作用。 Repeating the character sometimes works. 有时重复角色是可行的。 Different players work differently. 不同的玩家有不同的工作方式。 Experiment with Winamp and see what it likes, as that would be about as "official" as you can get. 试用Winamp并查看其喜欢之处,因为这可能与您获得的“官方”内容差不多。 Everything else is probably just a broken client. 其他一切可能只是一个坏客户。 (If you wanted to get really crafty, you could determine the client from the User-Agent request header and adjust your escaping accordingly.) (如果您想变得非常狡猾,则可以从User-Agent请求标头中确定客户端,并相应地调整转义。)

Now that you have the metadata block, you just need to add one byte to the front of it that indicates how long it is, divided by 16 . 现在您已经有了元数据块,您只需要在它的前面添加一个字节,指示它的长度,除以16 So if we now have a 64-byte metadata, we will add the byte 0x04 to the front of it which indicates that our metadata is 64 bytes long. 因此,如果现在拥有64字节的元数据,则将字节0x04添加到它的前面,这表明我们的元数据长度为64字节。 This gives in total a 65-byte metadata block that we now send to the client. 这总共提供了一个65字节的元数据块,我们现在将其发送给客户端。 Send it. 发送。

From here, we just enter the loop again, sending another 8KB of stream data before inserting metadata. 从这里开始,我们再次进入循环,在插入元数据之前发送另外8KB的流数据。 This time around though since we don't want to change the metadata, we just send 0x00 as our metadata block. 但这一次,因为我们不想更改元数据,所以我们只发送0x00作为我们的元数据块。 Again, since the first byte indicates the length of the chunk, and we're not updating the title, tell the client that the length is 0 . 同样,由于第一个字节指示块的长度,并且我们不更新标题,所以请告诉客户端该长度为0 We only send the strings when something is changing. 我们仅在发生变化时才发送字符串。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM