python实现m3u8链接文件播放

m3u8文件格式简单说明

维基百科定义M3U8 是 Unicode 版本的 M3U,用 UTF-8 编码。"M3U" 和 "M3U8" 文件都是苹果公司使用的 HTTP Live Streaming(HLS) 协议格式的基础,这种协议格式可以在 iPhone 和 Macbook 等设备播放。

m3u8 文件实质是一个播放列表(playlist),其可能是一个媒体播放列表(Media Playlist),或者是一个主列表(Master Playlist)。但无论是哪种播放列表,其内部文字使用的都是 utf-8 编码。

当 m3u8 文件作为媒体播放列表(Meida Playlist)时,其内部信息记录的是一系列媒体片段资源,顺序播放该片段资源,即可完整展示多媒体资源。CCTV6直播其格式如下所示:

#EXTM3U
#EXT-X-VERSION:3
#EXT-X-MEDIA-SEQUENCE:35232
#EXT-X-TARGETDURATION:10
#EXTINF:10.000,
cctv6hd-1549272376000.ts
#EXTINF:10.000,
cctv6hd-1549272386000.ts
#EXTINF:10.000,
cctv6hd-1549272396000.ts
#EXTINF:10.000,
cctv6hd-1549272406000.ts
#EXTINF:10.000,
cctv6hd-1549272416000.ts
#EXTINF:10.000,
cctv6hd-1549272426000.ts

对于点播来说,客户端只需按顺序下载上述片段资源,依次进行播放即可。而对于直播来说,客户端需要定时重新请求该 m3u8 文件,看下是否有新的片段数据需要进行下载并播放。

下载ts文件并播放

opencv可以直接解析ts文件,所以基本思想就是不断下载新的ts片段文件,并保存。然后用opencv解析读取视频。

'''
@Author: Angus Cai
@Date: 2019-05-21 20:35:01
@LastEditors: Angus Cai
@LastEditTime: 2019-05-21 21:56:07
@Description: 不断请求下载M3U8文件里的所有片段并播放
'''
# -*- coding: utf-8 -*-
# Created on 2018/3/22

import requests
import os,re
import time
import urllib.request
import cv2

header = {
    'User-Agent': 'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0);'
}
play_list = [] #记录播放列表
def play(url):
    download_path = os.getcwd() + "\download"
    if not os.path.exists(download_path):
        os.mkdir(download_path)
    while True:
        all_content = requests.get(url).text
        # print(all_content)
        file_line = all_content.splitlines()
        # print(file_line)
        if file_line[0] != "#EXTM3U":
            raise BaseException(u"非M3U8的链接")
        else:
            unknow = True
            for index, line in enumerate(file_line):
                if "EXTINF" in line:
                    unknow = False
                    print(file_line[index + 1])
                    request = urllib.request.Request(file_line[index + 1],headers=header)
                    response = urllib.request.urlopen(request)
                    print(response)
                    html = response.read()
                    file_name = file_line[index+1].split('/')[-1]
                    file_name = file_name.split('-')[-1]
                    print(file_name)
                    if file_name=="404_0.ts":
                        print("设备离线!!!!")
                        continue
     
                    with open(download_path + "\\" + file_name, 'wb') as f:
                        f.write(html)
                        print(len(html))
                        # f.flush()
                    
                    if file_name not in play_list:
                        play_list.append(file_name)
                        cap = cv2.VideoCapture(download_path + "\\" + file_name)
                        while True:
                            ret, frame = cap.read()
                            if not ret:
                                break
                            cv2.imshow("Test", frame)
                            time.sleep(1/30)
                            k = cv2.waitKey(1) & 0xff
                            if k == 27 : break
                        cap.release()
                        # os.remove(download_path + "\\" + file_name)
                    else:
                        # os.remove(download_path + "\\" + file_name)
                        continue
    # cap.release
    cv2.destroyAllWindows()
 
if __name__ == '__main__':
    play("http://ivi.bupt.edu.cn/hls/cctv6hd.m3u8")

多线程操作实现流畅播放

通过多进程与队列,一个进程下载ts文件并存在本地,同时将文件名放入队列;另一个进程从队列中取出文件名并进行读取。

'''
@Author: Angus Cai
@Date: 2019-05-22 08:23:34
@LastEditors: Angus Cai
@LastEditTime: 2019-05-22 09:49:29
@Description: 
'''
import multiprocessing as mp
import cv2
import time
import subprocess as sp
import numpy
import requests
import os,re
import time
import urllib.request

def queue_img_put(q, HD_VIDEO_URL, header, play_list):
    download_path = os.getcwd() + "\download"
    if not os.path.exists(download_path):
        os.mkdir(download_path)
    while True:
        if q.qsize() <= 100:
            all_content = requests.get(HD_VIDEO_URL).text
            # print(all_content)
            file_line = all_content.splitlines()
            # print(file_line)
            if file_line[0] != "#EXTM3U":
                raise BaseException(u"非M3U8的链接")
            else:
                unknow = True
                for index, line in enumerate(file_line):
                    if "EXTINF" in line:
                        unknow = False
                        # pd_url = url.rsplit("/", 1)[0] + "/" + file_line[index + 1]
                        # print(file_line[index + 1])
                        print("http://ivi.bupt.edu.cn/hls/"+file_line[index + 1])
                        request = urllib.request.Request("http://ivi.bupt.edu.cn/hls/"+file_line[index + 1],headers=header)
                        # request = urllib.request.Request(file_line[index + 1],headers=header)
                        response = urllib.request.urlopen(request)
                        # print(response)
                        html = response.read()
                        file_name = file_line[index+1].split('/')[-1]
                        file_name = file_name.split('-')[-1]
                        print(file_name)
                        if file_name=="404_0.ts":
                            print("设备离线!!!!")
                            continue
                        # result_file_name= url_list[i][length:][-10:-3]
                        # result_file_name= patt.findall(file_name)[0]
                        # print("正在处理%s"%result_file_name+".ts","共%s/%s项"%(i+1,url_length))
                        with open(download_path + "\\" + file_name, 'wb') as f:
                            f.write(html)
                            print(len(html))
                            f.flush()
                        
                        if file_name not in play_list:
                            play_list.append(file_name)
                            q.put(download_path + "\\" + file_name)
                        else:
                            # os.remove(download_path + "\\" + file_name)
                            continue
        else:
            play_list = []
            continue
 
def queue_img_get(q, window_name):
    cv2.namedWindow(window_name, flags=cv2.WINDOW_FREERATIO)
    # image = cv2.imread("./404.PNG")
    while True:
        # print("播放。。")
        if q.qsize() > 0:
            file_name = q.get()
            cap = cv2.VideoCapture(file_name)
            while True:
                ret,frame = cap.read()
                if not ret:
                    break
                
                # detected_image_array, detections = Detector(frame)
                cv2.imshow(window_name, frame)
                time.sleep(1/30)
                k = cv2.waitKey(1) & 0xff
                if k == 27 : break
            cap.release()
            os.remove(file_name)
        else:
            # print("等待中。。。")
            # cv2.imshow(window_name, image)
            # k = cv2.waitKey(1) & 0xff
            # if k == 27 : break
            continue
    cv2.destroyAllWindows()


def run():  # single camera
    HD_VIDEO_URL = "http://ivi.bupt.edu.cn/hls/cctv6hd.m3u8"
    window_name = "CCTV6"
    header = {
    'User-Agent': 'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0);'
    }
    play_list = []
    mp.set_start_method(method='spawn')  # init
    queue = mp.Queue(maxsize=100)
    processes = [mp.Process(target=queue_img_put, args=(queue, HD_VIDEO_URL, header, play_list)),
                 mp.Process(target=queue_img_get, args=(queue, window_name))]

    [setattr(process, "daemon", True) for process in processes]  # process.daemon = True
    [process.start() for process in processes]
    [process.join() for process in processes]

if __name__ == '__main__':
    run()