【问题标题】:Delphi video streaming Http Server [closed]Delphi 视频流 Http Server [关闭]
【发布时间】:2014-12-29 08:16:28
【问题描述】:

如何使用 Indy HTTP Server 实现以下功能。客户端访问http://server_name:port,服务器返回视频流,存储在http://server_name_video:port/video1.mpg

【问题讨论】:

  • 为什么不使用 Tembeddedwb 而不是 indy 来达到这个目的
  • TEmbeddedWB 是一个客户端浏览器。 OP 想要创建一个服务器。

标签: delphi video streaming


【解决方案1】:

TIdHTTPServer 本身不支持流媒体。您必须手动实现它。在您的OnCommandGet 事件处理程序中,根据需要将所需的值分配给AResponseInfo 参数,例如ContentTypeTransferEncoding,并保留ContentTextContentStream 属性未分配,然后调用AResponseInfo.WriteHeader() 到仅将响应标头发送到客户端,然后进入一个循环,将视频媒体数据以块的形式写入(根据RFC 2616 Section 3.6.1 Chunked Transfer Coding 中描述的格式),直到客户端断开连接或到达媒体末尾。例如:

procedure TForm1.IdHTTPServer1CommandGet(AContext: TIdContext; ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
var
  FS: TFileStream;
  Buf: TIdBytes;
  BufLen: Integer;
begin
  if ARequestInfo.Document <> '/' then
  begin
    AResponseInfo.ResponseNo := 404;
    Exit;
  end;
  FS := TFileStream.Create('video1.mpg', fmOpenRead or fmShareDenyWrite);
  try
    AResponseInfo.ResponseNo := 200;
    AResponseInfo.ContentType := 'video/mpeg';
    AResponseInfo.TransferEncoding := 'chunked';
    AResponseInfo.WriteHeader;

    SetLength(Buf, 1024);
    repeat
      BufLen := FS.Read(Buf[0], 1024);
      if BufLen < 1 then Break;
      AContext.Connection.IOHandler.WriteLn(IntToHex(BufLen, 1));
      AContext.Connection.IOHandler.Write(Buf, BufLen);
      AContext.Connection.IOHandler.WriteLn;
    until False;

    AContext.Connection.IOHandler.WriteLn('0');
    AContext.Connection.IOHandler.WriteLn;
  finally
    FS.Free;
  end;
end;

另一方面,如果您尝试从另一台服务器流式传输媒体,它会变得有点复杂。您必须向另一台服务器发送请求,接收响应,然后将数据转发给您的客户端。但是,TIdHTTP 不支持流媒体,因此很难将其用于此目的。您最终可能不得不直接使用 TIdTCPClient 并自己实现 HTTP 协议的必要部分,例如:

procedure TForm1.IdHTTPServer1CommandGet(AContext: TIdContext; ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
var
  Client: TIdTCPClient;
  Headers: TIdHeaderList;
  S, ResponseCode, ResponseText: string;
  Size: Int64;
  Strm: TIdTCPStream;    
begin
  if ARequestInfo.Document <> '/' then
  begin
    AResponseInfo.ResponseNo := 404;
    Exit;
  end;
  Client := TIdTCPClient.Create;
  try
    Client.Host := 'server_name_video';
    Client.Port := port;
    Client.Connect;
    try
      Client.IOHandler.WriteLn('GET /video1.mpg HTTP/1.0');
      Client.IOHandler.WriteLn('Host: server_name_video');
      Client.IOHandler.WriteLn;

      ResponseText := Client.IOHandler.ReadLn;
      Fetch(ResponseText);
      ResponseText := TrimLeft(ResponseText);
      ResponseCode := Fetch(ResponseText, ' ', False);
      ResponseCode := Fetch(ResponseCode, '.', False);

      if ResponseCode <> '200' then
      begin
        AResponseInfo.ResponseNo := StrToInt(ResponseCode);
        AResponseInfo.ResponseText := ResponseText;
        Exit;
      end;

      Headers := TIdHeaderList.Create(QuoteHTTP);
      try
        Headers.FoldLength := MaxInt;

        repeat
          s := Client.IOHandler.ReadLn;
          if s = '' then Break;
          Headers.Add(s);
        until False;

        Strm := TIdTCPStream.Create(AContext.Connection);
        try
          AResponseInfo.ResponseNo := 200;
          AResponseInfo.ContentType := Headers.Values['Content-Type'];

          if Pos('chunked', Headers.Values['Transfer-Encoding']) <> 0 then
          begin
            AResponse.TransferEncoding := 'chunked';
            AResponseInfo.WriteHeader;

            repeat
              s := Client.IOHandler.ReadLn;
              AContext.Connection.IOHandler.WriteLn(s);
              Size := StrToInt64('$'+Fetch(s, ';'));
              if Size = 0 then Break;
              Client.IOHandler.ReadStream(Strm, Size, False);
              s := Client.IOHandler.ReadLn;
              AContext.Connection.IOHandler.WriteLn(s);
            until false;

            repeat
              s := Client.IOHandler.ReadLn;
              AContext.Connection.IOHandler.WriteLn(s);
            until s = '';
          end
          else if Headers.IndexOfName('Content-Length') <> -1 then
          begin
            Size := StrToInt64(Headers.Values['Content-Length']);
            AResponseInfo.ContentLength := Size;
            AResponseInfo.WriteHeader;
            if Size > 0 then
              Client.IOHandler.ReadStream(Strm, Size, False);
          end else
          begin
            AResponseInfo.CloseConnection := true;
            AResponseInfo.WriteHeader;
            try
              Client.IOHandler.ReadStream(Strm, -1, True);
            except
              on E: EIdSocketError do begin
                if not (E.LastError in [10053, 10054, 10058]) then
                  raise;
              end;
            end;
          end;
        finally
          Strm.Free;
        end;
      finally
        Headers.Free;
      end;
    finally
      Client.Disconnect;
    end;
  finally
    Client.Free;
  end;
end;

当然,如果需要,您还必须实现 HTTP 身份验证、字节范围请求等内容。

更新:或者,您可以直接使用TIdHTTP,而不是直接使用TIdHTTP,只需给它一个输出TStream,它会按原样写回原始客户端写给。您可以为此目的使用TIdEventStream,或者编写自己的TStream 类,例如:

type
  TMyStream = class(TIdBaseStream)
  protected
    FHTTP: TIdHTTP;
    FClient: TIdIOHandler;
    FResponse: TIdHTTPResponseInfo;
    function IdRead(var VBuffer: TIdBytes; AOffset, ACount: Longint): Longint; override;
    function IdWrite(const ABuffer: TIdBytes; AOffset, ACount: Longint): Longint; override;
    function IdSeek(const AOffset: Int64; AOrigin: TSeekOrigin): Int64; override;
    procedure IdSetSize(ASize: Int64); override;
  public
    constructor Create(AHTTP: TIdHTTP; AClient: TIdIOHandler; AResponse: TIdHTTPResponseInfo);
    destructor Destroy; override;
  end;

constructor TMyStream.Create(AHTTP: TIdHTTP; AClient: TIdIOHandler; AResponse: TIdHTTPResponseInfo);
begin
  inherited Create;
  FHTTP := AHTTP;
  FClient := AClient;
  FResponse := AResponse;
end;

destructor TMyStream.Destroy;
begin
  if FResponse.HeaderHasBeenWritten then
  begin
    FClient.WriteLn('0');
    FClient.WriteLn('');
  end;
end;

function TMyStream.IdRead(var VBuffer: TIdBytes; AOffset, ACount: Longint): Longint;
begin
  Result := 0;
end;

function TMyStream.IdWrite(const ABuffer: TIdBytes; AOffset, ACount: Longint): Longint;
begin
  if not FResponse.HeaderHasBeenWritten then
  begin
    FResponse.ResponseNo := 200;
    FResponseInfo.ContentType := FHTTP.Response.ContentType;
    FResponse.TransferEncoding := 'chunked';
    FResponse.WriteHeader;
  end;
  FClient.WriteLn(IntToHex(IndyLength(ABuffer, ACount, AOffset)));
  FClient.Write(ABuffer, ACount, AOffset);
  FClient.WriteLn;
end;

function TMyStream.IdSeek(const AOffset: Int64; AOrigin: TSeekOrigin): Int64;
begin
  Result := 0;
end;

procedure TMyStream.IdSetSize(ASize: Int64);
begin
end;

procedure TForm1.IdHTTPServer1CommandGet(AContext: TIdContext; ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
var
  HTTP: TIdHTTP;
  Strm: TMyStream;
begin
  if ARequestInfo.Document <> '/' then
  begin
    AResponseInfo.ResponseNo := 404;
    Exit;
  end;
  HTTP := TIdHTTP.Create;
  try
    HTTP.HTTPOptions := HTTP.HTTPOptions + [hoNoProtocolErrorException];
    Strm := TMyStream.Create(HTTP, AContext.Connection.IOHandler, AResponseInfo);
    try
      HTTP.Get('http://server_name_video:'+IntToStr(port)+'/video1.mpg', Strm);
    finally
      Strm.Free;
    end;
    if not AResponseInfo.HeaderHasBeenWritten then
    begin
      AResponseInfo.ResponseNo := HTTP.ResponseCode;
      AResponseInfo.ResponseText := HTTP.ResponseText;
    end;
  finally
    HTTP.Free;
  end;
end;

或者,如果其他服务器支持chunked 响应,您可以:

  1. 使用新的TIdHTTP.OnChunkReceived 事件将每个接收到的块写入客户端,与上面类似,无需使用自定义TStream(您仍然必须向TIdHTTP.Get() 提供TStream。您可以使用TIdEventStream为此,只是不为其分配任何事件处理程序,因此数据被丢弃。这可能会在未来改变)。

  2. 启用TIdHTTP 的新hoNoReadChunked 标志,然后将原始数据从TIdHTTP.IOHandler 直接传送到客户端,例如使用TIdTCPStreamAContext.Connection.IOHandler.WriteStream()

New TIdHTTP flags and OnChunkReceived event

【讨论】:

  • 我不得不佩服你的承诺,雷米!
  • Remy,当我尝试下载文件时 Headers.Values['Content-Type'] = 'application/octet-stream'。在 Client.IOHandler.ReadStream(Strm, -1, True);引发异常 10053。您能帮我解决此错误吗?
  • 第二个例子是作为代理从另一个服务器转发数据。如果Content-Typeapplication/octet-stream,那么这就是其他服务器实际报告的内容。至于 10053 错误,这是正常行为。 AReadUntilDisconnect 参数设置为 True,10053 是 WSAECONNABORTED,这是断开连接期间可能出现的几个错误代码之一。 ReadStream() 当前不将 10053 视为断开连接(即 TODO 项),因此您必须手动处理它。我更新了我的示例以显示这一点。
  • 因为AResponseInfo.WriteHeader添加到标题Content-Length: 0一些播放器和浏览器的连接中断,异常10053。您需要改为AResponseInfo.WriteHeader使用AContext.Connection.IOHandler.WriteLn(...)发送标题。
  • 如果绕过WriteHeader(),需要手动设置AResponseInfo.HeaderHasBeenWritten=true,防止TIdHTTPServerOnCommandGet退出时调用WriteHeader()
猜你喜欢
  • 2022-01-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多