EasyDarwin流媒体服务器

软硬件环境

  • ubuntu 16.04
  • EasyDarwin latest

前言

EasyDarwin是基于Apple开源项目Darwin Streaming Server开发的流媒体服务器,支持RTSP点播、直播(推拉模式)、HLS直播等功能;经过几年的发展,项目已经非常稳定,而且相关扩展的项目也很多,形成了自己的项目族,具体可参考他们的官方网站http://www.easydarwin.org

编译安装

下载编译

git clone https://github.com/EasyDarwin/EasyDarwin
cd EasyDarwin
chmod a+x Buildit
./Buildit i386(或者x64,根据你的平台决定)

运行服务

源码编译完成后,会在i386目录下生成可执行文件easydarwin,默认的配置文件在WinNTSupport/easydarwin.xml

./easydarwin -c WinNTSupport/easydarwin.xml -d

easydarwin.xml包含了N多的配置项,你可以根据自己的需要进行修改。目前的话,你只需要知道视频存放路径movie_folder,默认值是Movies

备注

普通的mp4文件如果直接丢到Movies目录下,vlc是无法进行rtsp点播的。视频文件需要先借助第三方工具进行RTSP/RTP hint处理,这里推荐My MP4Box GUI,下载地址http://www.videohelp.com/software/My-MP4Box-GUI,视频处理非常简单,这里就不多说了。

easy_

相关错误码

| 响应吗 | 报文描述 | 定义 |
| :--- | :--------------------------------------- | ---- |
| 200 | Success OK 成功 | |
| 201 | Success Created 成功创建 | |
| 202 | Success Accepted 已接受用于处理,但处理尚未完成 | |
| 204 | Success No Content 已接收请求,但不存在要回送的信息 | |
| 206 | Success Partial Content 已接收请求,但要回送的信息不完整 | |
| 301 | Redirect Permanent Moved 请求的数据具有新的位置且更改是永久的。 | |
| 302 | Redirect Temp Moved 请求的数据临时具有不同 URI | |
| 303 | Redirect See Other 可在另一 URI 下找到对请求的响应 | |
| 305 | Use Proxy 必须通过位置字段中提供的代理来访问请求的资源 | |
| 400 | Client Bad Request 请求中有语法问题,或不能满足请求 | |
| 401 | Client Unauthorized 未授权客户端访问数据 | |
| 402 | Payment Required 需要付款,表示计费系统已有效 | |
| 403 | Client Forbidden 禁止, 即使有授权也不需要访问 | |
| 404 | Not Found 服务器找不到给定的资源 | |
| 405 | Method Not Allowed 请求的方法不支持 | |
| 407 | Proxy Authentication Required 代理认证请求,客户机首先必须使用代理认证自身 | |
| 408 | Request Timeout 请求超时 | |
| 409 | Conflict 请求冲突 | |
| 412 | Precondition Failed 前提条件失败 | |
| 415 | Unsupported Media Type 服务器拒绝服务请求,因为不支持请求实体的格式 | |
| 500 | Server Internal Error 内部错误,因为意外情况,服务器不能完成请求 | |
| 501 | Server Not Implemented 未执行,服务器不支持请求 | |
| 502 | Server Bad Gateway 错误网关,服务器接收到来自上游服务器的无效响应 | |
| 503 | Server Unavailable 由于临时过载或无法获得服务护,服务器无法处理请求 | |
| 505 | RTSP Version Not Supported 不支持的RTSP版本 | |
| 600 | Memcache Not Found 找不到Memcache服务器 | |
| 601 | Database Not Found 找不到Database 服务器 | |
| 602 | User Not Found 找不到用户信息(该用户不存在) | |
| 603 | Device Not Found 找不到设备信息(该设备不存在或者没有与请求用户绑定) | |
| 604 | Session Not Found 找不到会话信息(Session过期或者不存在) | |
| 605 | Service Not Found 找不到请求的服务模块 | |
| 620 | Password Error 密码错误 | |
| 621 | XML Parse Error XML解析失败 | |
| 622 | Permission Error 没有权限 | |

摄像头作为视频源进行rtsp直播

服务器端利用ffmpeg抓取摄像头数据,然后推送到easyDarwin

ffmpeg -f video4linux2 -s 720x576 -i /dev/video0 -f audio_device -f rtsp rtsp://127.0.0.1/live/test.sdp

客户端利用vlc播放

vlc rtsp://127.0.0.1:554/live/test.sdp

easy_02

参考文献

  • http://www.easydarwin.org/
  • http://www.360doc.com/content/11/0126/18/2633_89188287.shtml
  • http://www.easydarwin.org/article/EasyDarwin/43.html

Linux下编译Darwin Streaming Server

软硬件环境

  • ubuntu 16.04
  • Darwin Streaming Server 6.0.3

前言

Darwin Streaming Server是苹果公司开发的开源流媒体服务器,用C++语言编写,具有高性能、可扩展、模块化、跨平台等特性。

编译安装

源码下载

目前最高版本是6.0.3,也是好久没有更新了

http://dss.macosforge.org/

获取Linux平台的2个patch文件

http://download.csdn.net/detail/djstavav/9612758

编译

tar xvf DarwinStreamingSrvr6.0.3-Source.tar
cd DarwinStreamingSrvr6.0.3-Source
mv ../dss-6.0.3.patch .
mv ../dss-hh-20081021-1.patch .
patch -p1 < dss-6.0.3.patch
patch -p1 < dss-hh-20081021-1.patch
./Buildit install
cd DarwinStreamingSrvr6.0.3-Linux
sudo ./Install(这里会提示输入用户名及密码,在web管理中会使用到)

运行

安装成功后服务端程序自动运行,其它情况下也可以手动启动/usr/local/sbin/DarwinStreamingServer,如果想以debug模式启动,加入-d参数即可。服务端可供播放的视频文件存放于/usr/local/movies/,DSS配置文件是/etc/streaming/streamingserver.xml,日志文件存在于/var/streaming/logs,可登录http://192.168.95.134:1220(ip地址请自行修改)进行页面管理。

测试

现在用vlc来测试下DSS是否安装成功

dss_01

dss_02

dss_03

dss_04

dss_05

dss_06

dss_07

dss_08

dss_09

参考文献

  • http://dss.macosforge.org
  • http://blog.sina.com.cn/s/blog_6a4c492f0100lkq3.html

python3调用C动态库

软硬件环境

  • OS X EI Capitan
  • Python 3.5.1
  • GCC 4.9

前言

最近在做python3开发中,碰到了一个问题,需要通过调用C的一个动态链接库来获取相应的值。扒了扒网络,动手实践了下,形成此文。

准备C动态库

源码test.c

#include <stdio.h>
#include <stdlib.h>

char * printStr(const char *p,const char *q)
{
    printf("%s",p);
    printf("%s",q);
    return "djstava";
}

通过以下命令编译成动态链接库

gcc -fPIC -shared -o libtest.so test.c

python3中调用

要调用C库中的函数,需要用到ctypes这个模块

# -*- coding: utf-8 -*-
__author__ = 'djstava'

from ctypes import *

handle = cdll.LoadLibrary('libtest.so')
func = handle.printStr
func.argtypes = (c_char_p,c_char_p)
func.restype = c_char_p
tmp = handle.printStr("hello".encode("utf-8"),"world".encode("utf-8"))
print(tmp.decode("utf-8"))

程序执行结果

helloworlddjstava

程序解释

func.argtypes = (c_char_p,c_char_p)
func.restype = c_char_p

这2句是分别设置参数数据类型和返回值类型,如果不进行设置,直接调用的话,参数可以正常接收,但是返回值永远是个int值,传入的字符串参数必须为encode(“utf-8”),否则在c库中仅会打印为首字符

handle = cdll.LoadLibrary('libtest.so')
ret = handle.printStr("hello".encode("utf-8"),"world".encode("utf-8"))

关于其它数据类型的argtypes的设置,请查阅参考文献中的链接。

参考文献

https://docs.python.org/3/library/ctypes.html

python3解析XML文件

软硬件环境

  • Ubuntu 15.10 32bit
  • Python 3.5.1
  • PyQt 5.5.1

前言

Python解析XML的方法挺多,本文主要是利用ElementTree来完成。

实例讲解

解析XML

以如下的XML文件为例

<root>
    <version>1.0.04</version>
    <mysqlhost>10.10.10.240</mysqlhost>
    <mysqlport>3306</mysqlport>
    <mysqluser>root</mysqluser>
    <mysqlpassword>123456</mysqlpassword>
    <mysqldatabase>longjingdb</mysqldatabase>
    <mysqltable>mac</mysqltable>
    <mysqlstbtype>L6000</mysqlstbtype>
    <irdetokeytype>1</irdetokeytype>
    <printerhost>192.168.1.51</printerhost>
    <printerport>4001</printerport>
</root>

编写了一个类来解析,用一个字典来存放

class SYSXMLParser(object):
def __init__(self,file):
    self.xmlFile = file
    self.sysXMLDict = {}

def getSysXMLDict(self):
    tree = ET.parse(self.xmlFile)
    root = tree.getroot()

    for child in root.getchildren():
        self.sysXMLDict[child.tag] = child.attrib
        self.sysXMLDict[child.tag] = child.text

    return self.sysXMLDict

执行后打印的结果如下

{'mysqlstbtype': 'L6000', 'mysqlpassword': '123456', 'version': '1.0.04', 'printerhost': '192.168.1.51', 'printerport': '4001', 'mysqltable': 'mac', 'mysqldatabase': 'longjingdb', 'mysqlport': '3306', 'mysqluser': 'root', 'mysqlhost': '10.10.10.240', 'irdetokeytype': '1'}

创建XML

手头上刚好有个实例,提供一个文件夹,里面是一些烧录镜像文件,针对这个镜像文件夹,需要生成一个XML文件,XML指定各个镜像文件的名字、对应烧录的地址、还有镜像的路径和md5值。

# -*- coding: utf-8 -*-
__author__ = 'djstava@gmail.com'

import os
import sys
import xml.etree.ElementTree as ET

from common.constant import *
from checksum.md5 import *

FirstRoundImages = {'pmp.toc':PMP_ADDRESS,'secboot.toc':SECBOOT_ADDRESS,'secos.toc':SECOS_ADDRESS,'secosbak.toc':SECOS_BACK_ADDRESS,
                'u-boot.toc':UBOOT_ADDRESS,'u-bootbak.toc':UBOOT_BACK_ADDRESS,'splash.dat':SPLASH_ADDRESS}

SecondRoundImages = {'factorytest.img':FACTORYTEST_ADDRESS,'boot.img':BOOT_ADDRESS,'system.img':SYSTEM_ADDRESS,'dvbdata.img':DVBDATA_ADDRESS,
                 'userdata.img':USERDATA_ADDRESS,'cache.img':CACHE_ADDRESS,'otaloader.img':OTALOADER_ADDRESS,'iploader.img':IPLOADER_ADDRESS,
                 'recovery.img':RECOVERY_ADDRESS}

class GenerateConfigXML(object):
    firstRoundImageDict = {}
    secondRoundImageDict = {}

    def __init__(self,path):
        self.path = path

    def buildConfigXML(self):
        '''
        :param path: images dir
        :return:
        '''

        self.listDir(self.path)

        root = ET.Element("root")

        self.firstRound = ET.SubElement(root,"FirstRound")
        for image in self.firstRoundImageDict.keys():
            if image == "pmp.toc":
                imagePmp = ET.SubElement(self.firstRound,image)
                imagePmp.set("name",image)
                imagePmp.set("address",self.firstRoundImageDict[image])
                imagePmp.set("path",os.path.relpath(self.path + "/" + image))
                imagePmp.set("md5",CalcMD5.calcFileMd5(self.path + "/" + image))
                self.firstRoundImageDict.pop(image)
                break

        for image in self.firstRoundImageDict.keys():
            if image == "secboot.toc":
                imagePmp = ET.SubElement(self.firstRound,image)
                imagePmp.set("name",image)
                imagePmp.set("address",self.firstRoundImageDict[image])
                imagePmp.set("path",os.path.relpath(self.path + "/" + image))
                imagePmp.set("md5",CalcMD5.calcFileMd5(self.path + "/" + image))
                self.firstRoundImageDict.pop(image)
                break

        for image in self.firstRoundImageDict.keys():
            if image == "secos.toc":
                imagePmp = ET.SubElement(self.firstRound,image)
                imagePmp.set("name",image)
                imagePmp.set("address",self.firstRoundImageDict[image])
                imagePmp.set("path",os.path.relpath(self.path + "/" + image))
                imagePmp.set("md5",CalcMD5.calcFileMd5(self.path + "/" + image))
                self.firstRoundImageDict.pop(image)
                break

        for image in self.firstRoundImageDict.keys():
            if image == "secosbak.toc":
                imagePmp = ET.SubElement(self.firstRound,image)
                imagePmp.set("name",image)
                imagePmp.set("address",self.firstRoundImageDict[image])
                imagePmp.set("path",os.path.relpath(self.path + "/" + 'secos.toc'))
                imagePmp.set("md5",CalcMD5.calcFileMd5(self.path + "/" + 'secos.toc'))
                self.firstRoundImageDict.pop(image)
                break

        for (name,address) in self.firstRoundImageDict.items():
            if name == "pmp.toc":
                continue

            if name == "u-bootbak.toc":
                imageName = ET.SubElement(self.firstRound,name)
                imageName.set("name",name)
                imageName.set("address",address)
                imageName.set("path",os.path.relpath(self.path + "/u-boot.toc"))
                imageName.set("md5",CalcMD5.calcFileMd5(self.path + "/u-boot.toc"))
                continue

            imageName = ET.SubElement(self.firstRound,name)
            imageName.set("name",name)
            imageName.set("address",address)
            imageName.set("path",os.path.relpath(self.path + "/" + name))
            imageName.set("md5",CalcMD5.calcFileMd5(self.path + "/" + name))

        self.secondRound = ET.SubElement(root,"SecondRound")
        for (name,address) in self.secondRoundImageDict.items():
            imageName = ET.SubElement(self.secondRound,name)
            imageName.set("name",name)
            imageName.set("address",address)
            imageName.set("path",os.path.relpath(self.path + "/" + name))
            imageName.set("md5",CalcMD5.calcFileMd5(self.path + "/" + name))

        tree = ET.ElementTree(root)
        self.indent(root)

        if os.path.exists(XML_CONFIG_FILE):
            os.remove(XML_CONFIG_FILE)

        tree.write("config.xml")

    def listDir(self, path):
        for root,dirs,files in os.walk(path):
            for file in files:
                if file in FirstRoundImages.keys():
                    print("firstRound: " + file)
                    if file == 'secos.toc':
                        self.firstRoundImageDict[file] = FirstRoundImages[file]
                        self.firstRoundImageDict['secosbak.toc'] = FirstRoundImages['secosbak.toc']
                        continue

                    if file == 'u-boot.toc':
                        self.firstRoundImageDict[file] = FirstRoundImages[file]
                        self.firstRoundImageDict['u-bootbak.toc'] = FirstRoundImages['u-bootbak.toc']
                        continue

                    self.firstRoundImageDict[file] = FirstRoundImages[file]

                if file in SecondRoundImages.keys():
                    print("secondRound: " + file)
                    self.secondRoundImageDict[file] = SecondRoundImages[file]



    def indent(self, elem, level=0):
        i = "\n" + level*"  "
        if len(elem):
            if not elem.text or not elem.text.strip():
                elem.text = i + "  "
            for e in elem:
                self.indent(e, level+1)
            if not e.tail or not e.tail.strip():
                e.tail = i
        if level and (not elem.tail or not elem.tail.strip()):
            elem.tail = i

        return elem

if __name__ == '__main__':
    if len(sys.argv) < 2:
        print("Usage: python3 generateConfigXml.py dirOfTheImages")
        sys.exit(1)

    obj = GenerateConfigXML(sys.argv[1])
    obj.buildConfigXML()

最后生成的config.xml内容如下:

<root>

<FirstRound />

<SecondRound>

    <factorytest.img address="otaloaderbak" md5="2bca7c24acf471ad4126e63224b117c3" name="factorytest.img" path="factory20160411/factorytest.img" />
    <iploader.img address="iploader" md5="40d6bf4fe05bb4ce2d500464d48da5a4" name="iploader.img" path="factory20160411/iploader.img" />
    <boot.img address="boot" md5="b1937b921ee1122f1946dcb96811e69e" name="boot.img" path="factory20160411/boot.img" />
    <system.img address="system" md5="12fe7f8c8920c18a3cc9a815f201cca7" name="system.img" path="factory20160411/system.img" />
    <otaloader.img address="otaloader" md5="0831e96caf91482dbb9efde1ca29d3bd" name="otaloader.img" path="factory20160411/otaloader.img" />
    <userdata.img address="userdata" md5="536d084f75a46470f2373e3052d288dc" name="userdata.img" path="factory20160411/userdata.img" />

</SecondRound>

</root>

最后顺便提供下python3下的MD5计算方法,见下面这个类

# -*- coding: utf-8 -*-
__author__ = 'djstava@gmail.com'

import hashlib

class CalcMD5(object):
def __init__(self):
    pass

@classmethod
def calcFileMd5(self,filePath):
    '''
    :param filePath:
    :return: file checksum value
    '''

    md5 = hashlib.md5()

    fp = open(filePath,'rb')
    md5.update(fp.read())

    while True:
        block = fp.read(1048576)
        if not block:
            break
        md5.update(block)

    fp.close()
    return md5.hexdigest()

@classmethod
def calcStringMd5(self,str):
    '''
    :param str:
    :return: string checksum value
    '''

    return hashlib.md5(str.encode("utf-8")).hexdigest()

python3中的mysql数据库操作

软硬件环境

  • OS X EI Capitan
  • Python 3.5.1
  • mysql 5.6

前言

在开发中经常涉及到数据库的使用,而python对于数据库也有多种解决方法。本文以python3中的mysql为例,介绍pymysql模块的使用。

准备数据库

创建一个mysql数据库,名字叫testdb,建立一张表叫testtable,它有3个字段,分别是id,数据类型是INT(11),设为主键、非空、UNSIGNED、AUTO INCREMENT,name,数据类型是VARCHAR(45),设为非空、唯一,sex,数据类型是VARCHAR(45),设为非空

python3 源码

# -*- coding: utf-8 -*-
__author__ = 'djstava@gmail.com'

import logging
import pymysql

class MySQLCommand(object):
    def __init__(self,host,port,user,passwd,db,table):
        self.host = host
        self.port = port
        self.user = user
        self.password = passwd
        self.db = db
        self.table = table

    def connectMysql(self):
        try:
            self.conn = pymysql.connect(host=self.host,port=self.port,user=self.user,passwd=self.password,db=self.db,charset='utf8')
            self.cursor = self.conn.cursor()
        except:
            print('connect mysql error.')

    def queryMysql(self):
        sql = "SELECT * FROM " + self.table

        try:
            self.cursor.execute(sql)
            row = self.cursor.fetchone()
            print(row)

        except:
            print(sql + ' execute failed.')

    def insertMysql(self,id,name,sex):
        sql = "INSERT INTO " + self.table + " VALUES(" + id + "," + "'" + name + "'," + "'" + sex + "')"
        try:
            self.cursor.execute(sql)
        except:
            print("insert failed.")

    def updateMysqlSN(self,name,sex):
        sql = "UPDATE " + self.table + " SET sex='" + sex + "'" + " WHERE name='" + name + "'"
        print("update sn:" + sql)

        try:
            self.cursor.execute(sql)
            self.conn.commit()
        except:
            self.conn.rollback()
    def closeMysql(self):
        self.cursor.close()
        self.conn.close()