Android平台RTMP推送|轻量级RTSP服务能力封装代码实现

​好多开发者问我们,有没有针对Android平台RTMP直播推送、轻量级RTSP服务模块的进一步封装,可以更便捷的调用大牛直播SDK接口。

为此,我们分享下我们针对Android平台SmartPublisher做的二次封装代码:

ini 复制代码
package com.daniulive.smartpublisher;

import android.util.Log;

import java.nio.ByteBuffer;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class LibPublisherWrapper {
    private static String TAG = "NTLogLibPBW";
    private static final int OK = 0;

    private final ReadWriteLock rw_lock_ = new ReentrantReadWriteLock(true);
    private final java.util.concurrent.locks.Lock write_lock_ = rw_lock_.writeLock();
    private final java.util.concurrent.locks.Lock read_lock_ = rw_lock_.readLock();

    private SmartPublisherJniV2 lib_publisher_;
    private volatile long native_handle_;

    private volatile boolean is_rtmp_publishing_;
    private volatile boolean is_recording_;
    private volatile boolean is_rtsp_publishing_;
    private volatile boolean is_gb_stream_publishing_;

    private void clear_all_publishing_flags() {
        this.is_rtmp_publishing_ = false;
        this.is_recording_ = false;
        this.is_rtsp_publishing_ = false;
        this.is_gb_stream_publishing_ = false;
    }

    public void set(SmartPublisherJniV2 lib_publisher, long handle) {
        if (!empty())
            throw new IllegalStateException("it is not empty");

        if (handle != 0) {
            if (null == lib_publisher)
                throw new NullPointerException("lib_publisher is null");
        }

        write_lock_.lock();
        try {
            clear_all_publishing_flags();
            this.lib_publisher_ = lib_publisher;
            this.native_handle_ = handle;
        } finally {
            write_lock_.unlock();
        }

        Log.i(TAG, "set native_handle:" + handle);
    }

    @Override
    protected void finalize() throws Throwable {
        try {
            if (check_native_handle()) {
                if(is_rtmp_publishing()) {
                    lib_publisher_.SmartPublisherStopPublisher(get());
                    this.is_rtmp_publishing_ = false;
                }

                if(is_recording()) {
                    lib_publisher_.SmartPublisherStopRecorder(get());
                    this.is_recording_ = false;
                }

                if (is_rtsp_publishing()) {
                    lib_publisher_.StopRtspStream(get());
                    this.is_rtsp_publishing_ = false;
                }

                if (is_gb_stream_publishing()) {
                    lib_publisher_.StopGB28181MediaStream(get());
                    this.is_gb_stream_publishing_ = false;
                }

                lib_publisher_.SmartPublisherClose(this.native_handle_);
                Log.i(TAG, "finalize close handle:" + this.native_handle_);
                this.native_handle_ = 0;
            }
        }catch (Exception e) {

        }

        super.finalize();
    }

    public void release() {
        if (empty())
            return;

        if(is_rtmp_publishing())
            StopPublisher();

        if (is_recording())
            StopRecorder();

        if (is_rtsp_publishing())
            StopRtspStream();

        if (is_gb_stream_publishing())
            StopGB28181MediaStream();

        long handle;
        write_lock_.lock();
        try {
            handle = this.native_handle_;
            this.native_handle_ = 0;
            clear_all_publishing_flags();
        } finally {
            write_lock_.unlock();
        }

        if (lib_publisher_ != null && handle != 0)
            lib_publisher_.SmartPublisherClose(handle);
    }

    public boolean try_release() {
        if (empty())
            return false;

        if (is_publishing()) {
            Log.i(TAG, "try_release it is publishing, native_handle:" + get());
            return false;
        }

        long handle;
        write_lock_.lock();
        try {
            if (is_publishing())
                return false;

            handle = this.native_handle_;
            this.native_handle_ = 0;
        } finally {
            write_lock_.unlock();
        }

        if (lib_publisher_ != null && handle != 0)
            lib_publisher_.SmartPublisherClose(handle);

        return true;
    }

    public final boolean empty() { return 0 == this.native_handle_; }

    private final long get() { return this.native_handle_; }

    private final boolean check_native_handle() {
        return this.lib_publisher_ != null && this.native_handle_ != 0;
    }

    public final boolean is_rtmp_publishing() { return is_rtmp_publishing_; }

    public final boolean is_recording() { return is_recording_; }

    public final boolean is_rtsp_publishing() { return is_rtsp_publishing_; }

    public final boolean is_gb_stream_publishing() { return is_gb_stream_publishing_; }

    public final boolean is_publishing() { return is_gb_stream_publishing_ || is_rtmp_publishing_ || is_rtsp_publishing_ || is_recording_; }

    public boolean SetMute(boolean is_mute) {
        if (!check_native_handle())
            return false;

        return OK == lib_publisher_.SmartPublisherSetMute(get(), is_mute? 1 : 0);
    }

    public boolean SetInputAudioVolume(int index, float volume) {
        if (!check_native_handle())
            return false;

        return OK == lib_publisher_.SmartPublisherSetInputAudioVolume(get(), index , volume);
    }

    public boolean SetMirror(boolean is_mirror) {
        if (!check_native_handle())
            return false;

        return OK == lib_publisher_.SmartPublisherSetMirror(get(), is_mirror?1:0);
    }

    public boolean SetRecorderDirectory(String path) {
        if (!check_native_handle())
            return false;

        return OK == lib_publisher_.SmartPublisherSetRecorderDirectory(get(), path);
    }

    public boolean SetRecorderFileMaxSize(int size) {
        if (!check_native_handle())
            return false;

        return OK == lib_publisher_.SmartPublisherSetRecorderFileMaxSize(get(), size);
    }

    public boolean CaptureImage(int compress_format, int quality, String file_name, String user_data_string) {
        if (!check_native_handle())
            return false;

        return OK == lib_publisher_.CaptureImage(get(), compress_format, quality, file_name, user_data_string);
    }

    public boolean SetURL(String url) {
        if (!check_native_handle())
            return false;

        return OK == lib_publisher_.SmartPublisherSetURL(get(), url);
    }

    public boolean StartPublisher() {
        if (!check_native_handle())
            return false;

        if (is_rtmp_publishing()) {
            Log.e(TAG, "already publishing rtmp, native_handle:" + get());
            return false;
        }

        int ret = lib_publisher_.SmartPublisherStartPublisher(get());
        if (ret != OK) {
            Log.e(TAG, "call SmartPublisherStartPublisher failed, native_handle:" + get() + ", ret:" + ret);
            return false;
        }

        write_lock_.lock();
        try {
            this.is_rtmp_publishing_ = true;
        } finally {
            write_lock_.unlock();
        }

        Log.i(TAG, "call SmartPublisherStartPublisher OK, native_handle:" + get());
        return true;
    }

    public boolean StopPublisher() {
        if (!check_native_handle())
            return false;

        if (!is_rtmp_publishing()) {
            Log.w(TAG, "it's not publishing rtmp, native_handle:" + get());
            return false;
        }

        boolean is_need_call = false;
        write_lock_.lock();
        try {
            if (this.is_rtmp_publishing_) {
                this.is_rtmp_publishing_ = false;
                is_need_call = true;
            }
        } finally {
            write_lock_.unlock();
        }

        if (is_need_call)
            lib_publisher_.SmartPublisherStopPublisher(get());

        return true;
    }

    public boolean StartRecorder() {
        if (!check_native_handle())
            return false;

        if (is_recording()) {
            Log.e(TAG, "already recording, native_handle:" + get());
            return false;
        }

        int ret = lib_publisher_.SmartPublisherStartRecorder(get());
        if (ret != OK) {
            Log.e(TAG, "call SmartPublisherStartRecorder failed, native_handle:" + get() + ", ret:" + ret);
            return false;
        }

        write_lock_.lock();
        try {
            this.is_recording_ = true;
        } finally {
            write_lock_.unlock();
        }

        Log.i(TAG, "call SmartPublisherStartRecorder OK, native_handle:" + get());
        return true;
    }

    public boolean PauseRecorder(boolean is_pause) {
        if (!check_native_handle())
            return false;

        if (!is_recording()) {
            Log.e(TAG, "it's not recording, native_handle:" + get());
            return false;
        }

        return OK == lib_publisher_.SmartPublisherPauseRecorder(get(), is_pause?1:0);
    }

    public boolean StopRecorder() {
        if (!check_native_handle())
            return false;

        if (!is_recording()) {
            Log.w(TAG, "it's not recording, native_handle:" + get());
            return false;
        }

        boolean is_need_call = false;
        write_lock_.lock();
        try {
            if (this.is_recording_) {
                this.is_recording_ = false;
                is_need_call = true;
            }
        } finally {
            write_lock_.unlock();
        }

        if (is_need_call)
            lib_publisher_.SmartPublisherStopRecorder(get());

        return true;
    }

    public boolean SetRtspStreamName(String stream_name) {
        if (is_null_or_empty(stream_name))
            return false;

        if (!check_native_handle())
            return false;

        return OK == lib_publisher_.SetRtspStreamName(get(), stream_name);
    }

    public boolean AddRtspStreamServer(long rtsp_server_handle) {
        if (!check_native_handle())
            return false;

        return OK == lib_publisher_.AddRtspStreamServer(get(), rtsp_server_handle, 0);
    }

    public boolean ClearRtspStreamServer() {
        if (!check_native_handle())
            return false;

        return OK == lib_publisher_.ClearRtspStreamServer(get());
    }

    public boolean StartRtspStream() {
        if (!check_native_handle())
            return false;

        if (is_rtsp_publishing()) {
            Log.e(TAG, "already publishing rtsp stream, native_handle:" + get());
            return false;
        }

        int ret = lib_publisher_.StartRtspStream(get(), 0);
        if (ret != OK) {
            Log.e(TAG, "call StartRtspStream failed, native_handle:" + get() + ", ret:" + ret);
            return false;
        }

        write_lock_.lock();
        try {
            this.is_rtsp_publishing_ = true;
        } finally {
            write_lock_.unlock();
        }

        Log.i(TAG, "call StartRtspStream OK, native_handle:" + get());
        return true;
    }

    public boolean StopRtspStream() {
        if (!check_native_handle())
            return false;

        if (!is_rtsp_publishing()) {
            Log.w(TAG, "it's not publishing rtsp stream, native_handle:" + get());
            return false;
        }

        boolean is_need_call = false;
        write_lock_.lock();
        try {
            if (this.is_rtsp_publishing_) {
                this.is_rtsp_publishing_ = false;
                is_need_call = true;
            }
        } finally {
            write_lock_.unlock();
        }

        if (is_need_call)
            lib_publisher_.StopRtspStream(get());

        return true;
    }

    public boolean SetGB28181RTPSender(long rtp_sender_handle, int rtp_payload_type, String encoding_name) {
        if (!check_native_handle())
            return false;

        return OK == lib_publisher_.SetGB28181RTPSender(get(), rtp_sender_handle, rtp_payload_type, encoding_name);
    }

    public boolean StartGB28181MediaStream() {
        if (!check_native_handle())
            return false;

        if (is_gb_stream_publishing()) {
            Log.e(TAG, "already publishing gb media stream, native_handle:" + get());
            return false;
        }

        int ret = lib_publisher_.StartGB28181MediaStream(get());
        if (ret != OK) {
            Log.e(TAG, "call StartGB28181MediaStream failed, native_handle:" + get() + ", ret:" + ret);
            return false;
        }

        write_lock_.lock();
        try {
            this.is_gb_stream_publishing_ = true;
        } finally {
            write_lock_.unlock();
        }

        Log.i(TAG, "call StartGB28181MediaStream OK,native_handle:" + get());

        return true;
    }

    public boolean StopGB28181MediaStream() {
        if (!check_native_handle())
            return false;

        if (!is_gb_stream_publishing()) {
            Log.w(TAG, "it's not publishing gb stream, native_handle:" + get());
            return false;
        }

        boolean is_need_call = false;
        write_lock_.lock();
        try {
            if (this.is_gb_stream_publishing_) {
                this.is_gb_stream_publishing_ = false;
                is_need_call = true;
            }
        } finally {
            write_lock_.unlock();
        }

        if (is_need_call)
            lib_publisher_.StopGB28181MediaStream(get());

        return true;
    }

    public boolean OnPCMData(ByteBuffer pcm_data, int size, int sample_rate, int channel, int per_channel_sample_number) {
        if (!check_native_handle() || !is_publishing())
            return false;

        if (!read_lock_.tryLock())
            return false;

        try {
            if (!check_native_handle() || !is_publishing())
                return false;

            return OK == lib_publisher_.SmartPublisherOnPCMData(get(), pcm_data, size, sample_rate, channel, per_channel_sample_number);
        } catch (Exception e) {
            Log.e(TAG, "OnPCMData Exception:", e);
            return false;
        } finally {
            read_lock_.unlock();
        }
    }

    public boolean OnFarEndPCMData(ByteBuffer pcm_data, int sample_rate, int channel, int per_channel_sample_number, int is_low_latency) {
        if (!check_native_handle() || !is_publishing())
            return false;

        if (!read_lock_.tryLock())
            return false;

        try {
            if (!check_native_handle() || !is_publishing())
                return false;

            return OK == lib_publisher_.SmartPublisherOnFarEndPCMData(get(), pcm_data, sample_rate, channel, per_channel_sample_number, is_low_latency);
        } catch (Exception e) {
            Log.e(TAG, "OnFarEndPCMData Exception:", e);
            return false;
        } finally {
            read_lock_.unlock();
        }
    }

    public boolean PostUserUTF8StringData(String utf8_string) {
        if (is_null_or_empty(utf8_string))
            return false;

        if (!check_native_handle() || !is_publishing())
            return false;

        if (!read_lock_.tryLock())
            return false;

        try {
            if (!check_native_handle() || !is_publishing())
                return false;

            return OK == lib_publisher_.SmartPublisherPostUserUTF8StringData(get(), utf8_string, 0);
        } catch (Exception e) {
            Log.e(TAG, "PostUserUTF8StringData Exception:", e);
            return false;
        } finally {
            read_lock_.unlock();
        }
    }

    public boolean OnCaptureVideoData(byte[] data, int len, int cameraType, int curOrg) {
        if (!check_native_handle())
            return false;

        if (!read_lock_.tryLock())
            return false;

        try {
            if (!check_native_handle())
                return false;

            return OK == lib_publisher_.SmartPublisherOnCaptureVideoData(get(), data, len, cameraType, curOrg);
        } catch (Exception e) {
            Log.e(TAG, "OnCaptureVideoData Exception:", e);
            return false;
        } finally {
            read_lock_.unlock();
        }
    }

    public boolean EnableLayer(int index, boolean is_enable) {
        if (index < 1)
            return false;

        if (!check_native_handle())
            return false;

        read_lock_.lock();

        try {
            if (!check_native_handle())
                return false;

            return OK == lib_publisher_.EnableLayer(get(), index, is_enable?1:0);
        } catch (Exception e) {
            Log.e(TAG, "EnableLayer Exception:", e);
            return false;
        } finally {
            read_lock_.unlock();
        }
    }

    public boolean RemoveLayer(int index) {
        if (index < 1)
            return false;

        if (!check_native_handle())
            return false;

        read_lock_.lock();

        try {
            if (!check_native_handle())
                return false;

            return OK == lib_publisher_.RemoveLayer(get(), index);
        } catch (Exception e) {
            Log.e(TAG, "RemoveLayer Exception:", e);
            return false;
        } finally {
            read_lock_.unlock();
        }
    }

    public boolean PostLayerImageNV21ByteArray(int index, int left, int top,
                                               byte[] y_plane, int y_offset, int y_row_stride,
                                               byte[] uv_plane, int uv_offset, int uv_row_stride,
                                               int width, int height, int is_vertical_flip,  int is_horizontal_flip,
                                               int scale_width,  int scale_height, int scale_filter_mode,
                                               int rotation_degree) {
        if (!check_native_handle())
            return false;

        if (!read_lock_.tryLock())
            return false;

        try {
            if (!check_native_handle())
                return false;

            return OK == lib_publisher_.PostLayerImageNV21ByteArray(get(), index, left, top, y_plane, y_offset, y_row_stride,
                    uv_plane, uv_offset, uv_row_stride, width, height, is_vertical_flip, is_horizontal_flip,
                    scale_width, scale_height, scale_filter_mode, rotation_degree);
        } catch (Exception e) {
            Log.e(TAG, "PostLayerImageNV21ByteArray Exception:", e);
            return false;
        } finally {
            read_lock_.unlock();
        }
    }

    public boolean PostLayerImageRGBA8888ByteBuffer(int index, int left, int top,
                                                    ByteBuffer rgba_plane, int offset, int row_stride, int width, int height,
                                                    int is_vertical_flip,  int is_horizontal_flip,
                                                    int scale_width,  int scale_height, int scale_filter_mode,
                                                    int rotation_degree) {

        if (!check_native_handle())
            return false;

        if (!read_lock_.tryLock())
            return false;

        try {
            if (!check_native_handle())
                return false;

            return OK == lib_publisher_.PostLayerImageRGBA8888ByteBuffer(get(), index, left, top,
                    rgba_plane, offset, row_stride, width, height, is_vertical_flip, is_horizontal_flip,
                    scale_width, scale_height, scale_filter_mode, rotation_degree);

        } catch (Exception e) {
            Log.e(TAG, "PostLayerImageRGBA8888ByteBuffer Exception:", e);
            return false;
        } finally {
            read_lock_.unlock();
        }
    }

    public boolean PostLayerImageI420ByteBuffer(int index, int left, int top,
                                                ByteBuffer y_plane, int y_offset, int y_row_stride,
                                                ByteBuffer u_plane, int u_offset, int u_row_stride,
                                                ByteBuffer v_plane, int v_offset, int v_row_stride,
                                                int width, int height, int is_vertical_flip,  int is_horizontal_flip,
                                                int scale_width,  int scale_height, int scale_filter_mode,
                                                int rotation_degree) {
        if (!check_native_handle())
            return false;

        if (!read_lock_.tryLock())
            return false;

        try {
            if (!check_native_handle())
                return false;

            return OK == lib_publisher_.PostLayerImageI420ByteBuffer(get(), index, left, top,
                    y_plane, y_offset, y_row_stride,
                    u_plane, u_offset, u_row_stride,
                    v_plane, v_offset, v_row_stride,
                    width, height, is_vertical_flip, is_horizontal_flip,
                    scale_width, scale_height, scale_filter_mode,
                    rotation_degree);

        } catch (Exception e) {
            Log.e(TAG, "PostLayerImageI420ByteBuffer Exception:", e);
            return false;
        } finally {
            read_lock_.unlock();
        }
    }

    public boolean PostLayerImageYUV420888ByteBuffer(int index, int left, int top,
                                                     ByteBuffer y_plane, int y_offset, int y_row_stride,
                                                     ByteBuffer u_plane, int u_offset, int u_row_stride,
                                                     ByteBuffer v_plane, int v_offset, int v_row_stride, int uv_pixel_stride,
                                                     int width, int height, int is_vertical_flip,  int is_horizontal_flip,
                                                     int scale_width,  int scale_height, int scale_filter_mode,
                                                     int rotation_degree) {
        if (!check_native_handle())
            return false;

        if (!read_lock_.tryLock())
            return false;

        try {
            if (!check_native_handle())
                return false;

            return OK == lib_publisher_.PostLayerImageYUV420888ByteBuffer(get(), index, left, top, y_plane, y_offset, y_row_stride,
             u_plane, u_offset, u_row_stride, v_plane, v_offset, v_row_stride, uv_pixel_stride,
             width, height, is_vertical_flip, is_horizontal_flip, scale_width, scale_height, scale_filter_mode, rotation_degree);

        } catch (Exception e) {
            Log.e(TAG, "PostLayerImageYUV420888ByteBuffer Exception:", e);
            return false;
        } finally {
            read_lock_.unlock();
        }
    }

    public boolean PostAudioEncodedData(int codec_id, ByteBuffer data, int size, int is_key_frame,
                                                      long timestamp,ByteBuffer parameter_info, int parameter_info_size) {
        if (!check_native_handle())
            return false;

        if (!read_lock_.tryLock())
            return false;

        try {
            if (!check_native_handle())
                return false;

            return OK == lib_publisher_.SmartPublisherPostAudioEncodedData(get(), codec_id, data, size, is_key_frame, timestamp, parameter_info, parameter_info_size);

        } catch (Exception e) {
            Log.e(TAG, "PostLayerImageYUV420888ByteBuffer Exception:", e);
            return false;
        } finally {
            read_lock_.unlock();
        }
    }

    private static boolean is_null_or_empty(String val) {
        return null == val || val.isEmpty();
    }

    //估计硬编码码率, 可以根据实际机型调整
    public static int estimate_video_hardware_kbps(int width, int height, int fps, boolean is_h264) {
        int kbps;
        int area = width * height;
        if (area <= (320 * 300))
            kbps = is_h264?350:280;
        else if (area <= (370 * 320))
            kbps = is_h264?470:400;
         else if (area <= (640 * 360))
            kbps = is_h264?850:650;
       else if (area <= (640 * 480))
            kbps = is_h264?1200:800;
        else if (area <= (800 * 600))
            kbps = is_h264?1300:950;
        else if (area <= (900 * 700))
            kbps = is_h264?1600:1100;
        else if (area <= (1280 * 720))
            kbps = is_h264?2100:1500;
         else if (area <= (1366 * 768))
            kbps = is_h264?2300:1900;
         else if (area <= (1600 * 900))
            kbps = is_h264?2800:2300;
        else if (area <= (1600 * 1050))
            kbps =is_h264?3100:2500;
         else if (area <= (1920 * 1088))
            kbps = is_h264?4200:2800;
        else
            kbps = is_h264?4500:3500;

        kbps = (int)(kbps*fps*1.0/25.0 + 0.5);
        return kbps;
    }

    public static int estimate_video_software_quality(int width, int height, boolean is_h264) {
        int area = width*height;
        int quality;
        if ( area <= (320 * 240) )
            quality = is_h264? 23 : 27;
        else if ( area <= (640 * 360) )
            quality = is_h264? 25 : 28;
        else if ( area <= (640 * 480) )
            quality = is_h264? 26 : 28;
        else if ( area <= (960 * 600) )
            quality = is_h264? 26 : 28;
        else if ( area <= (1280 * 720) )
            quality = is_h264? 27 : 29;
        else if ( area <= (1600 * 900) )
            quality = is_h264 ? 28 : 30;
        else if ( area <= (1920 * 1088) )
            quality = is_h264 ? 29 : 31;
        else
            quality = is_h264 ? 30 : 32;

        return quality;
    }

    public static int estimate_video_vbr_max_kbps(int width, int height, int fps) {
        int kbps;
        int area = width*height;
        if (area <= (320 * 300))
            kbps = 320;
        else if (area <= (360 * 320))
            kbps = 400;
        else if (area <= (640 * 360))
            kbps = 700;
        else if (area <= (640 * 480))
            kbps = 800;
        else if (area <= (800 * 600))
            kbps = 900;
        else if (area <= (900 * 700))
            kbps = 1100;
        else if (area <= (1280 * 720))
            kbps = 1700;
        else if (area <= (1366 * 768))
            kbps = 1800;
        else if (area <= (1600 * 900))
            kbps = 2500;
        else if (area <= (1600 * 1050))
            kbps = 2700;
        else if (area <= (1920 * 1088))
            kbps = 3100;
        else
            kbps = 3500;

        kbps = (int)(kbps*fps*1.0/25.0 + 0.5);

        return kbps;
    }
}

封装后的LibPublisherWrapper类,逻辑分离更彻底,调用更方便,几乎不要花心思了解接口用法,就可以非常高效的实现RTMP推送或轻量级RTSP服务技术诉求。

相关推荐
u1301305 天前
深入理解 M3U8 与 HLS 协议:从原理到实战解析
前端·音视频开发·流媒体·hls·m3u8
aqi0013 天前
FFmpeg开发笔记(九十九)基于Kotlin的国产开源播放器DKVideoPlayer
android·ffmpeg·kotlin·音视频·直播·流媒体
字节架构前端14 天前
媒体采集标准草案 与 Chromium 音频采集实现简介
前端·chrome·音视频开发
Tiny_React18 天前
使用 Claude Code Skills 模拟的视频生成流程
人工智能·音视频开发·vibecoding
aqi0019 天前
FFmpeg开发笔记(九十八)基于FFmpeg的跨平台图形用户界面LosslessCut
android·ffmpeg·kotlin·音视频·直播·流媒体
aqi0020 天前
FFmpeg开发笔记(九十七)国产的开源视频剪辑工具AndroidVideoEditor
android·ffmpeg·音视频·直播·流媒体
aqi0021 天前
FFmpeg开发笔记(一百)国产的Android开源视频压缩工具VideoSlimmer
android·ffmpeg·音视频·直播·流媒体
haibindev23 天前
【终极踩坑指南】Windows 10上MsQuic证书加载失败?坑不在证书,而在Schannel!
直播·http3·quic·流媒体
飞鸟真人1 个月前
livekit搭建与使用浏览器测试
直播·视频会议·视频聊天·livekit
hk11241 个月前
【音视频/边缘计算】2025年度H.265/HEVC高并发解码与画质修复(Super-Resolution)基准测试报告(含沙丘/失控玩家核心样本)
ffmpeg·边缘计算·音视频开发·h.265·测试数据集