直播与点播的对比、直播与视频聊天的对比

直播传输的是视频流,而点播是播放的录制好的视频文件
在这里插入图片描述
直播与视频聊天的对比:我觉得直播是通过流媒体技术把现场的图像和声音采集后分发出去,供观众在线围观,可以认为是开放的,一般没有人数限制;而视频聊天是点对点的线上交流,是基于UDP/TCP的实时传输协议实现的。
当然,两者也是可以结合的,比如:在直播过程中跟某个观众连麦。
在这里插入图片描述

视频流全链路技术

一、视频流媒体基础概念

  1. 流媒体本质
    流媒体是通过互联网实时传输音视频内容的技术,用户无需等待完整下载即可开始观看,核心是将内容分割为小数据包顺序传输。

  2. 关键处理流程
    ‌推流‌:将视频流上传至服务器(如RTMP推送直播画面)
    ‌转码‌:改变视频编码格式/参数以适应不同终端(如H265转H264)
    ‌分发‌:将处理后的视频流通过多种协议传输给终端用户

二、核心协议与技术标准

  1. 传输协议
    ‌RTMP‌:Adobe推出的低延迟协议,适合推流但需Flash支持
    ‌HLS‌:苹果开发的基于HTTP的流媒体协议,使用M3U8索引文件和TS分片
    ‌WebRTC‌:支持浏览器实时通信的开放标准,无需插件
    ‌RTSP‌:用于控制媒体服务器,常与RTP配合传输流数据
  2. 编码标准
    ‌H.264/AVC‌:最广泛兼容的编码格式,浏览器原生支持
    ‌H.265/HEVC‌:压缩效率提升50%,但需要终端解码支持
    ‌VP9/AV1‌:开源编码格式,适合Web环境但编码复杂度高

三、视频处理关键技术方案

  1. 转码实现方式
    ‌云端转码‌:利用云服务(如阿里云、腾讯云)处理,无需自建基础设施
    ‌本地转码‌:使用FFmpeg等工具自主处理,灵活但资源消耗大
# FFmpeg转码示例:生成HLS流
ffmpeg -i input.mp4 -hls_time 10 -hls_list_size 0 output.m3u8
  1. 转码技术原理
    ‌解封装‌:分离容器中的音视频流(如MP4中的H264视频和AAC音频)
    ‌解码‌:将压缩数据还原为原始帧(YUV视频/PCM音频)
    ‌处理‌:缩放、裁剪、滤镜等操作
    ‌编码‌:按目标格式重新压缩(如H264转H265)
    ‌封装‌:将流重新打包为目标容器格式(如TS分片)
  2. 播放器技术选型
    ‌H5 Video‌:原生video标签,兼容性有限但无需插件
    ‌Video.js‌:开源HTML5播放器,支持HLS/DASH等协议
    ‌JWPlayer‌:商业级解决方案,功能全面但需付费

四、典型架构设计方案

  1. 轻量级解决方案
[摄像头] --RTSP--> [Nginx+RTMP模块] --HLS--> [浏览器]
           ↑
        [FFmpeg转码]
  1. 企业级视频平台
[设备] --GB28181--> [Liveweb汇聚平台] --WebRTC/FLV--> [多终端]
    ├─ 云端录像存储
    ├─ AI分析接入
    └─ 多协议转换:ml-citation{ref="1" data="citationList"}

五、实践建议与优化方向
‌协议选择‌:优先考虑HLS+WebRTC组合,平衡兼容性与延迟
‌转码策略‌:根据终端能力动态选择输出格式(如移动端用H264)
‌性能优化‌:使用硬件加速(如Intel QSV/NVIDIA NVENC)提升转码速度

直播demo的实现小结

1. 基于rtmp和http-flv的直播链路
[obs推流软件] --RTMP--> [流媒体服务] --HLS--> [VLC播放器或flv.js]
  1. 流媒体服务:在gitee上找到了一个基于netty的java版rtmp服务器 https://gitee.com/kabuzhu/srsj
    具体代码逻辑在后续研究。目前只是将其本地运行,充当流媒体服务。

  2. 配置obs的推流
    媒体源可以是本地视频文件,也可以是视频采集设备等多种形式。
    在这里插入图片描述

  3. 配置vlc的播放链接
    在这里插入图片描述

  4. 也可以用flv.js在浏览器上播放

<!DOCTYPE html>
<html>
	<head>
		<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
		<script src="https://cdn.bootcdn.net/ajax/libs/flv.js/1.6.2/flv.min.js"></script>

		<style>
		body,center{

		padding:0;

		margin:0;

		}

		   .v-container{

			   width:640px;

			   height:360px;

		border:solid 1px red;

		   }

		video{

		width:100%;

		height:100%;

		}

		</style>

	</head>
	<body>
		 <div class="v-container">
			<video id="videoElement" muted autoplay="autoplay" preload="auto" controls="controls">
			</video>
		 </div>
	</body>
</html>

<script>
if (flvjs.isSupported()) {
       var videoElement = document.getElementById('videoElement');
       var flvPlayer = flvjs.createPlayer({
		   type: 'flv', // 指定视频类型,特别注意此处为flv
		   isLive: true, // 开启直播
		   hasAudio: false, //需要设置为false不然播放不了视频
		   cors: true,  // 开启跨域访问
		   url: 'http://127.0.0.1:8762/myapp/stream.flv'
	   },
	   {
		   enableWorker: false, //不启用分离线程
		   enableStashBuffer: false, //关闭IO隐藏缓冲区
		   reuseRedirectedURL: true, //重用301/302重定向url,用于随后的请求,如查找、重新连接等。
		   autoCleanupSourceBuffer: true //自动清除缓存
	   });
       

       flvPlayer.attachMediaElement(videoElement);
       flvPlayer.load();
       flvPlayer.play();
       // flvPlayer.stop();
   }
</script>
2. 基于HLS协议的直播链路
  1. 理解m3u8文件

M3U8 文件是 HLS (HTTP Live Streaming) 协议的核心组件,它本质上是一个播放列表文件,包含媒体流的结构信息。在直播场景中,M3U8 文件确实像一个动态滑动的窗口,不断更新以反映最新的媒体分片

#EXTM3U
#EXT-X-VERSION:3     文件头标识, 用于标识文件类型和协议版本
#EXT-X-TARGETDURATION:13 	目标时长,指所有分片的最大时长(向上取整)
#EXT-X-MEDIA-SEQUENCE:29	媒体序列号,当前播放列表的起始分片序号
#EXTINF:10.416667           分片信息,包含每个分片的时长和文件名
segment029.ts


#EXT-X-ENDLIST           	结束标记(仅限点播)

点播 vs 直播的 M3U8 差异

点播包含所有分片信息,序列号固定为1,含有结束标记
直播中的分片信息是动态更新的,序列号会递增,无结束标记表示直播仍在继续

  1. 使用ffmpeg模拟ts文件和m3u8文件的生成
ffmpeg -re -stream_loop -1 -i /home/ftpuser/tc/data/trailer.mp4 -c:v copy -c:a copy -f hls -hls_time 10 -hls_list_size 5 -hls_flags delete_segments -hls_segment_filename "/home/ftpuser/tc/data/20250619/2005/success_trailer_prefix-%04d.ts" -hls_base_url "http://192.168.0.37:9091/tc/data/20250619/2005/" /home/ftpuser/tc/data/20250619/2005/trailer_playlist.m3u8
  1. 编写shell脚本,自己来根据ts文件动态生成m3u8文件
    由于获取ts文件的时长需要依赖ffmpeg,所以需要事先安装。另外,该脚本将hls_base_url的前缀部分作为了入参。
    你可以根据自己的需求编写脚本。
#!/bin/bash
set -euo pipefail

# 检查参数
if [ $# -ne 1 ]; then
    echo "Usage: $0 <hls_base_url>"
    exit 1
fi
hls_base_url="$1"
base_dir="/home/ftpuser/tc/data"

# 获取日期函数
get_date() {
    date -d "$1" +%Y%m%d
}

# 获取TS时长函数 - 使用ffprobe
get_duration() {
    local file="$1"
    local default_duration=10.000000
    local duration

    # 使用ffprobe尝试获取时长
    if duration=$(ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 "$file" 2>/dev/null); then
        # 检查获取到的duration是否为数字
        if [[ "$duration" =~ ^[0-9]+(\.[0-9]+)?$ ]]; then
            printf "%.6f" "$duration"
            return 0
        fi
    fi

    # 如果上面获取失败,则使用默认时长
    printf "%.6f" "$default_duration"
    return 0
}

# 状态存储
declare -A stream_status  # key: dir:prefix -> "last_seq last_timestamp ended"

# 主循环
while true; do
    # 获取监控目录(前一天和当天)
    watch_dirs=(
        "$base_dir/$(get_date 'yesterday')/2005"
        "$base_dir/$(get_date 'today')/2005"
    )
    current_time=$(date +%s)

    for dir in "${watch_dirs[@]}"; do
        # 跳过不存在的目录
        [ -d "$dir" ] || continue
        
        # 初始化目录流数组
        declare -A prefix_files=()
        
        # 扫描所有TS文件
        while IFS= read -r file; do
            filename=$(basename "$file")
            
            # 解析前缀和序号
            if [[ "$filename" =~ ^(.+)-([0-9]{4})\.ts$ ]]; then
                prefix="${BASH_REMATCH[1]}"
                sequence="${BASH_REMATCH[2]#0}"  # 去除前导零
                sequence="${sequence#0}"          # 多次处理确保
                sequence="${sequence#0}"
                sequence=$((10#$sequence))       # 转为十进制
                
                key="${dir}:${prefix}"
                # 初始化数组元素
                if [ -z "${prefix_files[$key]:-}" ]; then
                    prefix_files["$key"]=""
                fi
                prefix_files["$key"]="${prefix_files[$key]} $sequence:$file"
            fi
        done < <(find "$dir" -maxdepth 1 -type f -name '*-[0-9][0-9][0-9][0-9].ts' 2>/dev/null)

        # 处理每个流
        for key in "${!prefix_files[@]}"; do
            dir="${key%:*}"
            prefix="${key#*:}"
            m3u8_file="${dir}/${prefix}.m3u8"
            
            # 初始化流状态
            if [ -z "${stream_status[$key]:-}" ]; then
                stream_status[$key]="0 0 0"  # last_seq, last_time, ended
            fi
            
            # 解析状态
            read -r last_seq last_timestamp ended <<< "${stream_status[$key]}"
            
            # 确保时间戳是数字
            last_timestamp=${last_timestamp:-0}
            last_seq=${last_seq:-0}
            ended=${ended:-0}
            
            # 跳过已结束的流
            if [ "$ended" -eq 1 ]; then
                continue
            fi

            # 收集有效TS文件(已写完的)
            valid_files=()
            max_seq=0
            for item in ${prefix_files[$key]}; do
                seq="${item%%:*}"
                file="${item#*:}"
                
                # 检查文件是否正在写入
                if ! lsof -w -- "$file" >/dev/null 2>&1; then
                    valid_files+=("$seq:$file")
                    if [ "$seq" -gt "$max_seq" ]; then
                        max_seq="$seq"
                    fi
                fi
            done

            # 按序号排序
            mapfile -t sorted_files < <(printf '%s\n' "${valid_files[@]}" | sort -t: -k1,1n)
            
            # 处理新文件
            new_files=()
            for item in "${sorted_files[@]}"; do
                seq="${item%%:*}"
                file="${item#*:}"
                
                if [ -n "$seq" ] && [ -n "$last_seq" ] && [ "$seq" -gt "$last_seq" ]; then
                    new_files+=("$item")
                fi
            done

            # 更新M3U8文件
            if [ "${#new_files[@]}" -gt 0 ] || [ "$last_seq" -eq 0 ]; then
                # 读取现有分片(如果存在)
                existing_segments=()
				media_sequence=0
                if [ -f "$m3u8_file" ]; then
                    # 正确读取现有分片信息(EXTINF行和TS行)
                    while IFS= read -r line1 && IFS= read -r line2; do
                        if [[ "$line1" == "#EXTINF:"* && "$line2" == *".ts" ]]; then
                            existing_segments+=("$line1" "$line2")
                        fi
                    done < <(awk '/#EXTINF:/{getline line; print $0; print line}' "$m3u8_file")
                fi
                
                # 添加新分片到现有分片列表
                for item in "${new_files[@]}"; do
                    seq="${item%%:*}"
                    file="${item#*:}"
                    duration=$(get_duration "$file")
                    rel_path="${dir#$base_dir/}"
                    ts_url="$hls_base_url/$rel_path/$(basename "$file")"
                    existing_segments+=("#EXTINF:$duration," "$ts_url")
                done
                
                # 只保留最后5个分片(10行:5个EXTINF + 5个TS)
                if [ "${#existing_segments[@]}" -gt 10 ]; then
                    existing_segments=("${existing_segments[@]:${#existing_segments[@]}-10}")
                fi

				# 如果媒体序列号未设置,计算最小序列号
				if [ "$media_sequence" -eq 0 ]; then
					# 确保数组至少有一个完整的分片(EXTINF + URL)
					if [ "${#existing_segments[@]}" -ge 2 ]; then
						# 尝试从第一个分片URL中提取序列号
						first_ts_url="${existing_segments[1]}"
						
						if [[ "$first_ts_url" =~ -([0-9]{4})\.ts$ ]]; then
							sequence_str=${BASH_REMATCH[1]}
							media_sequence=$((10#$sequence_str))
						else
							media_sequence=1
						fi
					else
						# 没有分片,使用默认序列号
						media_sequence=1
					fi
				fi
				
                # 生成M3U8内容
                tmp_file=$(mktemp)
                {
                    echo "#EXTM3U"
                    echo "#EXT-X-VERSION:3"
                    echo "#EXT-X-TARGETDURATION:15"
					echo "#EXT-X-MEDIA-SEQUENCE:$media_sequence"
                    
                    # 输出所有分片
                    for ((i=0; i<${#existing_segments[@]}; i++)); do
                        echo "${existing_segments[$i]}"
                    done
                } > "$tmp_file"

                # 替换原文件
                mv -f "$tmp_file" "$m3u8_file"
                chmod 644 "$m3u8_file"
                
                # 更新状态
                last_seq="$max_seq"
                last_timestamp="$current_time"
                stream_status["$key"]="$last_seq $last_timestamp 0"
            fi

            # 检查超时 - 确保时间戳是有效的数字
            if [ -n "$last_timestamp" ] && [ "$last_timestamp" -gt 0 ] && 
               [ "$((current_time - last_timestamp))" -ge 25 ]; then
                # 添加ENDLIST标记
                if [ -f "$m3u8_file" ] && ! grep -q '#EXT-X-ENDLIST' "$m3u8_file"; then
                    echo "#EXT-X-ENDLIST" >> "$m3u8_file"
                    chmod 644 "$m3u8_file"
                fi
                stream_status["$key"]="$last_seq $last_timestamp 1"
            fi
        done
    done
    
    # 每秒执行一次
    sleep 1
done
  1. 使用hls.js进行直播的观看
<!DOCTYPE html>
<html>
<head>
    <title>HLS live</title>
    <link href="https://vjs.zencdn.net/7.20.3/video-js.css" rel="stylesheet">
</head>
<body>
    <h1>HLS live</h1>
    
    <video 
        id="live-player"
        class="video-js vjs-default-skin vjs-big-play-centered"
        controls
        preload="auto"
        width="1280"
        height="720"
        data-setup='{}'
    >
        <source src="http://192.168.0.37:9091/tc/data/20250619/2005/playlist.m3u8" type="application/x-mpegURL">
    </video>
    
    <script src="https://vjs.zencdn.net/7.20.3/video.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/videojs-contrib-hls@5.15.0/dist/videojs-contrib-hls.min.js"></script>
    
    <script>
        // 自动重连逻辑
        const player = videojs('live-player');
        
        player.on('error', () => {
            console.log('播放错误,尝试重新加载...');
            setTimeout(() => {
                player.src({src: 'http://192.168.0.37:9091/tc/data/20250619/2005/playlist.m3u8', type: 'application/x-mpegURL'});
                player.play();
            }, 3000);
        });
        
        // 每30秒刷新播放列表
        setInterval(() => {
            player.trigger('loadstart');
        }, 30000);
    </script>
</body>
</html>
Logo

GitCode 天启AI是一款由 GitCode 团队打造的智能助手,基于先进的LLM(大语言模型)与多智能体 Agent 技术构建,致力于为用户提供高效、智能、多模态的创作与开发支持。它不仅支持自然语言对话,还具备处理文件、生成 PPT、撰写分析报告、开发 Web 应用等多项能力,真正做到“一句话,让 Al帮你完成复杂任务”。

更多推荐