android - 带身份验证的 MediaPlayer RTSP 视频流

标签 android video-streaming android-mediaplayer rtsp

我可以在未经授权的情况下从网络摄像机流式传输视频,但现在我需要获得授权才能执行此操作。我发现很少有信息表明 Android 不支持通过 RTSP 进行身份验证,但我发现另一条信息表明可以通过使用该方法添加 HEADERS 在 API 级别 14 中实现:setDataSource (Context context, Uri uri, Map headers) .我的代码如下所示:

@Override
public void surfaceCreated(SurfaceHolder holder){
    String authHeader = getB64Auth("user","password");
    Map<String, String> headers = new HashMap<String, String>();
    headers.put("Authorization", authHeader);
    Uri srcUri = Uri.parse("rtsp://10.0.0.113:554/channel1");
        try{
            m.setDisplay(h);
            m.setDataSource (getApplicationContext(), srcUri,headers);
            m.prepare();
            m.setAudioStreamType(AudioManager.STREAM_MUSIC);
            m.start();
        }catch(Exception e){
            e.printStackTrace();
        }

}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
    m.release();
}
private String getB64Auth (String login, String pass) {
    String source=login+":"+pass;
    String ret="Basic "+Base64.encodeToString(source.getBytes(),Base64.URL_SAFE|Base64.NO_WRAP);
    Log.e("Authorization",ret);
    return ret;
}

但它不起作用,我找不到问题出在哪里。有人对这种流媒体有任何经验吗?或者我只是误解了 MediaPlayer 类的新方法的作用?

最佳答案

RTSP 使用 TCP 控制套接字来执行握手和发送控制消息。实际视频数据通过 TCP 或 UDP 中的单独端口发送。 RTSP 控制信息也可以通过 HTTP 或 HTTPS 传输。我在使用 Android MediaPlayer 时遇到的问题是它不支持 RTSP 身份验证。但是,我能够通过创建添加身份验证信息的本地 RTSP 代理来绕过该限制。幸运的是,RTSP header 是非常简单明了的文本,因此添加必要的身份验证很容易。

这是我的做法:

  1. 创建一个本地 ServerSocket 作为代理。
  2. 根据用户密码构建身份验证字符串
  3. 将 MediaPlayer 连接到您的本地代理
  4. 对于传出的数据,将认证添加到传出的TCP包中

请注意,RTSP 数据非常稀疏,因此解析和更改所有 RTSP 数据的开销通常可以忽略不计。

下面是一些示例代码来说明我的意思:

import android.util.Base64;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.HashMap;
import java.util.Map;

public class RTSPConnection implements Runnable {

    private final IRTSPListener listener;

    private String targetRtspServerHost;
    private String targetRtspServerProto;
    private String targetRtspUserInfo;
    private String targetRtspServerPath;
    private int targetRtspServerPort = -1;

    public RTSPConnection(IRTSPListener l, String u) throws RTSPException {
        // Parse the host (content provider) from the URL
        this.listener = l;

        if (u == null) {
            throw new RTSPException();
        }

        URI parsedURL;
        try {
            parsedURL = new URI(u);
        } catch (URISyntaxException e) {
            throw new RTSPException();
        }

        targetRtspServerHost = parsedURL.getHost();     // none is null
        if (targetRtspServerHost == null || targetRtspServerHost.equals("")) {
            throw new RTSPException();
        }

        targetRtspServerProto = parsedURL.getScheme();  // none is null
        if (targetRtspServerProto == null) {
            targetRtspServerProto = "rtsp";             // default to rtsp:
        }
        if (!targetRtspServerProto.equalsIgnoreCase("rtsp")) {
            throw new RTSPException();                  // error for http: or https:
        }

        targetRtspServerPort = parsedURL.getPort();     // none is -1
        if (targetRtspServerPort == -1) {
            targetRtspServerPort = 554;                 // Use the RTSP default port
        }

        targetRtspUserInfo = parsedURL.getUserInfo();   // none is null

        targetRtspServerPath = parsedURL.getPath();     // none is an empty string ""
    }

    @Override
    public void run() {
        ServerSocket listen;

        try {
            // Start the local proxy server on a random high port
            listen = new ServerSocket(0);
        } catch (IOException e) {
            // Error starting local server
        }

        // Connects to the RTSP server
        Socket remote;
        try {
            remote = new Socket(targetRtspServerHost, targetRtspServerPort);
        } catch (IOException e) {
            try {
                listen.close();
            } catch (IOException ignored) {}
            return;
        }

        // Notify user on other thread that the server is about to start
        // Build the string that the VideoPlayer should connect to in order to be routed to the actual source
        int listeningOnPort = listen.getLocalPort();
        String connectUrl = targetRtspServerProto + "://localhost:" + listeningOnPort + targetRtspServerPath;
        listener.onRtspProxyReady(connectUrl);

        // Wait for a local connection (Blocking)
        Socket local;
        try {
            // Wait for 5 seconds for a connection
            listen.setSoTimeout(5000);
            local = listen.accept();
        } catch (IOException e) {
            // Error on server connect/accept or timed out waiting
            try {
                listen.close();
            } catch (IOException ignored) {}
            try {
                remote.close();
            } catch (IOException ignored) {}
            listener.onRtspProxyError(RTSPError.ProxyTimedOut);
            return;
        }

        // Create the Authorization header for the outgoing RTSP packets
        Map<String, String> headers = null;
        if (targetRtspUserInfo != null) {
            String authHeader = "Basic " + Base64.encodeToString(targetRtspUserInfo.getBytes(),
                    Base64.URL_SAFE | Base64.NO_WRAP);
            headers = new HashMap<String, String>();
            headers.put("Authorization", authHeader);
        }

        try {
            new ForwardAndAddHeadersThread(local, remote, headers).start();
            new PassThru(remote, local).start();
        } catch (IOException e) {
            try {
                local.close();
            } catch (IOException ignored) {}
            try {
                listen.close();
            } catch (IOException ignored) {}
            try {
                remote.close();
            } catch (IOException ignored) {}
            listener.onRtspProxyError(RTSPError.CouldNotStartProxy);
        }
    }

    private abstract class ForwardTCPThread extends Thread {
        protected InputStream in;
        protected OutputStream out;
        private Socket socket_in, socket_out;

        public ForwardTCPThread(Socket i, Socket o) throws IOException {
            socket_in = i;
            socket_out = o;
            in = socket_in.getInputStream();
            out = socket_out.getOutputStream();
        }

        protected void shutdown() {
            // Close things down...
            if (in != null) {
                try {
                    in.close();
                } catch (IOException ignored) {}
            }
            if (out != null) {
                try {
                    out.close();
                } catch (IOException ignored) {}
            }

            try {
                socket_in.close();
            } catch (IOException ignored) {}
            try {
                socket_out.close();
            } catch (IOException ignored) {}

        }
    }

    private class PassThru extends ForwardTCPThread {

        public PassThru(Socket in, Socket out) throws IOException {
            super(in, out);
            setName("Forward TCP");
        }

        public void run() {
            byte[] buf = new byte[4096];
            try {
                int count;
                while ((count = in.read(buf)) > 0) {
                    out.write(buf, 0, count);
                }
            } catch (IOException e) {
                listener.onRtspProxyError(RTSPError.RemoteConnectionDropped);
            }

            shutdown();
        }
    }

    public class ForwardAndAddHeadersThread extends ForwardTCPThread {
        private final Map<String,String> headers;

        public ForwardAndAddHeadersThread(Socket in, Socket out, Map<String,String> headers) throws IOException {
            super(in, out);
            this.headers = headers;
            setName("Forward TCP Add Headers");
        }

        public void run() {
            byte[] buf = new byte[4096];
            try {
                int count;
                /*
                 * This code looks for the sequence number header in the RTSP packet and inserts additional headers
                 * on the next rows, separated by \r\n
                 */
                while ((count = in.read(buf)) > 0) {
                    /**
                     * Note: This code is NOT optimized for speed.  It is assumed to be a very low data channel that
                     * only contains infrequent/short TCP/RTSP commands.
                     *
                     * Warn: This code assumes that the RTSP packet is read all-at-once, such that the CSeq: header
                     * is never split into two "reads".
                     */
                    String temp = new String(buf, 0, count, "UTF-8");
                    String strings[] = temp.split("\r\n",-1);
                    String str_buf = "";
                    for (String s: strings) {
                        str_buf += s + "\r\n";
                        if (headers != null) {
                            if (s.contains("CSeq:")) {
                                for (Map.Entry<String, String> entry : headers.entrySet()) {
                                    str_buf += entry.getKey() + ": " + entry.getValue() + "\r\n";
                                }
                            }
                        }
                    }
                    out.write(str_buf.getBytes("UTF-8"), 0, str_buf.length());
                }
            } catch (IOException e) {
                listener.onRtspProxyError(RTSPError.LocalConnectionDropped);
            }

            shutdown();
        }
    }
}

关于android - 带身份验证的 MediaPlayer RTSP 视频流,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/19979818/

相关文章:

iphone - 使用 ffmpeg 在 iPhone 上显示视频

Android 媒体播放器错误代码 (-38,0)

android - Android MediaPlayer无法播放声音

Android:如何告诉 ShrinkResources 保留某些资源

android - 如何确保我的 Android 应用仅在 Galaxy Tab 上可用?

objective-c - 如何提高 MPVolumeView 性能?

javascript - 通过 JavaScript 接口(interface)将图像从 Android 传递到 HTML (WebView)

android - 如何更改 android videoview 中流式传输的缓冲区大小?

android - 如何在android上的 "Open with"列表中显示我的应用程序

java - 获取数组列表中缺失的数字