转码和流式传输音频 - 如何发送内容范围标头

Posted

技术标签:

【中文标题】转码和流式传输音频 - 如何发送内容范围标头【英文标题】:Transcoding and streaming audio - how to send content-range headers 【发布时间】:2012-12-27 14:21:22 【问题描述】:

快速版:不知道正文长度时如何发送正确的Content-Range 标头?

我有一个 FLAC 文件。我想将其转码为 MP3 并立即流式传输给用户。 到目前为止,我有这样的事情:

function transcode(file) 
  var spawn = require('child_process').spawn

  var decode = spawn('flac', [
    '--decode',
    '--stdout',
    file
  ])

  var encode = spawn('lame', [
    '-V0',
    '-',
    '-'
  ])

  decode.stdout.pipe(encode.stdin)

  return encode


var express = require('express')
var app = express()

app.get('/somefile.mp3', function (req, res) 
  res.setHeader('Accept-Ranges', 'bytes')
  res.setHeader('Content-Range', 'bytes')
  res.setHeader('Content-Type', 'audio/mpeg')
  transcode(file).stdout.pipe(res)
)

这按预期工作,但它是“流式传输”,所以我不能跳过。 显然我需要做Content-Range 的东西。 使用:https://github.com/visionmedia/node-range-parser

function sliceStream(start, writeStream, readStream) 
  var length = 0
  var passed = false

  readStream.on('data', function (buf) 
    if (passed) return writeStream.write(buf);

    length += buf.length

    if (length < start) return;

    passed = true
    writeStream.write(buf.slice(length - start))
  )

  readStream.on('end', function () 
    writeStream.end()
  )


var parseRange = require('range-parser')

app.get('/somefile.mp3', function (req, res) 
  var ranges = parseRange(Infinity, req.headers['range'])

  if (ranges === -1 || ranges === -2) return res.send(400);

  var start = ranges[0].start

  res.setHeader('Accept-Ranges', 'bytes')
  res.setHeader('Content-Type', 'audio/mpeg')

  if (!start) 
    res.setHeader('Content-Range', 'bytes')
    transcode(file).stdout.pipe(res)
    return
  

  res.setHeader('Content-Range', 'bytes ' + start + '-')
  sliceStream(start, transcode(file).stdout, res)
)

这就是我卡住的地方。 因为我不会等到整首歌被编码后, 我不知道这首歌的大小。 由于我刚刚在 Chrome 中获得“取消”, 我假设 Content-Range 标头格式不正确,没有大小。

另外,我目前只是在浏览器中打开这首歌,所以我假设它使用了 &lt;audio&gt; 元素。

建议?

【问题讨论】:

【参考方案1】:

是的,您的 Content-Range 标头格式不正确,没有大小。但是,您可以尝试发送您的服务器已经转码的当前大小。虽然我怀疑 Chrome 是否会优雅地处理不断变化的大小……

There are a number of things you're not handling:

    您似乎没有发送206 Partial Content 状态(可能这是由图书馆处理的,不确定)。 看起来您甚至没有检查范围请求的结尾部分。除了0-,Chrome 通常不会发送任何内容,但其他浏览器会发送。事实上,有些人可能会在一个请求中发送多个范围(支持非常痛苦)。 您没有发送正确的Content-Range 响应标头,因为您也没有包含您要发送的内容的结束索引。它应该看起来像这样:Content-Range: bytes 0-2048/3980841 最后,如果客户端发出的范围请求超出范围(即范围值均不与资源范围重叠),服务应以416 Requested Range Not Satisfiable 状态响应。

编辑:我没有测试过这种特殊情况,但是如果您要从 FLAC 转码为 192kbps CBR MP3,我想如果您使用的可能性有限正在发送稍微不准确的内容长度(相差不到 1000 位):

音频的最后部分会被播放器截断。大约 1000 位会剪辑大约 5ms 的音频(对人类来说并不明显)。 浏览器将忽略结束索引或内容长度,并继续接受和/或请求超出您最初响应的 Content-Range 的范围,直到您关闭连接或发送 416 状态。 音频的丢失/错误结束可能会导致&lt;audio&gt; 引发MEDIA_ERR_NETWORKMEDIA_ERR_DECODE 错误,您只需优雅地处理即可。 (在这种情况下,音频仍会被剪辑。)

【讨论】:

所以基本上这意味着我必须事先知道长度? 基本上是的。但是,如果您进行 CBR 编码,您可以轻松估计长度。您会知道转码文件的长度(以毫秒为单位)和恒定比特率。 如果不准确怎么办?有关系吗? 编辑了我的答案来解释一下。

以上是关于转码和流式传输音频 - 如何发送内容范围标头的主要内容,如果未能解决你的问题,请参考以下文章

Android 上的音频流

流式传输实时音频

如何实时流式传输音频文件

iOS:如何将音频数据从客户端流式传输到服务器?

android将音频流式传输到服务器

将实时 Android 音频流式传输到服务器