前言:
现如今,大部分带内容的网站或应用都有视频区了,不说是大厂平台,就连个人开发者也相继在自己网站或小程序上迭代出视频板块。那既然有了视频模块,除个性化推荐,智能审核等这种费钱又耗时的功能外(个人开发者暂缓)。最基本的视频上传,视频播放自然必不可少吧。
既然要强调省钱,我当前不会对接点播服务了。毕竟为了有一定的审核和推荐功能,我打算做人工审核。那剩下的关于播放有一定的体验度,还得要用一下OSS了(还是要花一点嘛)。因为上传有现成的分片上传,播放有HLS流,以下着重讲关于视频播放的优化,上传部分就说一下思路哦。
视频上传:
因为网站关于上传只有一些图片,所以只用了OSS的常规文件上传。但是视频,不说长视频,现在一些稍微几十秒的短视频动则几十兆上百兆,更别说高清的。而且早期的上传是放在服务端完成,所以当客户端提交大型文件时,不光nginx(413 Request Entity Too Large)和fpm会报错,到了OSS客户端调用也会报错(Allowed memory size of 268435456 bytes exhausted (tried to allocate 67108896 bytes))。
开始是尝试在服务端改成分片上传,但是测试时发现,每次提交文件过来时都要先将视频本地化,存在服务器上后再分片提交到OSS,回调成功删除服务器文件,并且要调整nginx等的提交的最大值限制。最后就决定把上传部分放到了前端,服务器只提供OSS的临时访问凭证和接收上传成功回调。
视频播放:
关于视频播放,网站早期的做法是将OSS的视频地址直接丢到前端的video标签中,当在手机播放时会出现卡顿或播放缓慢等问题。最后决定使用OSS的HLS的构建,就是在后台将视频推流一份在OSS的LiveChannel中,前端通过读取m3u8播放视频。
思路:
1. 前端上传视频,后台审核成功调用OSS的LiveChannel创建。
2. 根据通道创建返回的推流地址和播放地址,存入数据库。
3. 在服务端起一个进程用FFMPEG进行视频推流。
4. 前端播放视频时选择m3u8的方式。
示例代码:
1. 前端
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>普通上传模板,需要修改成分片上传</title> <script src="https://code.jquery.com/jquery-3.1.1.min.js"></script> <style> #content { border: 1px solid saddlebrown; padding: 16px; border-radius: 2px } .list { top: 15px; width: 140px; height: 40px; border: 1px solid #0082E6; display: inline-block; border-radius: 2px; position: relative; line-height: 40px; } #file { position: absolute; opacity: 0; color: white; width: 100%; height: 100%; z-index: 100; } .list span { display: inline-block; text-align: center; width: 100%; line-height: 40px; position: absolute; color: #0082E6; } video { margin-top: 8px; border-radius: 4px; } ._p { margin: 14px; } ._p input { display: inline-block; width: 70%; margin-left: 6px; } ._p span { font-size: 15px; } </style> </head> <body> <div id="content"> <p class="_p"><span>视频标题</span>: <input id="title" type="text" class="form-control" placeholder="请输入视频名称"> </p> <p class="_p"> <span>选择视频: </span> <!--文件选择按钮--> <a class="list" href="javascript:;"> <input id="file" type="file" name="myfile" onchange="UpladFile();"/> <span>选择视频</span> </a> <!--上传速度显示--> <span id="time"></span> </p> <!--显示消失--> <ul class="el-upload-list el-upload-list--text" style="display: none;"> <li tabindex="0" class="el-upload-list__item is-success"> <a class="el-upload-list__item-name"> <i class="el-icon-document"></i> <span id="videoName">food.jpeg</span> </a> <label class="el-upload-list__item-status-label"> <i class="el-icon-upload-success el-icon-circle-check"></i> </label> <i class="el-icon-close" onclick="del();"></i> <i class="el-icon-close-tip"></i> </li> </ul> <!--进度条--> <div class="el-progress el-progress--line" style="display: none;"> <div class="el-progress-bar"> <div class="el-progress-bar__outer" style="height: 6px;"> <div class="el-progress-bar__inner" style="width: 0%;"> </div> </div> </div> <div class="el-progress__text" style="font-size: 14.4px;">0%</div> </div> <p class="_p"><span>上传视频</span>: <button class="btn btn-primary" type="button" onclick="sub();">上传</button> </p> <!--预览框--> <div class="preview"> </div> </div> <script> var xhr;//异步请求对象 var ot; //时间 var oloaded;//大小 //上传文件方法 function UpladFile() { var fileObj = document.getElementById("file").files[0]; // js 获取文件对象 if (fileObj.name) { $(".el-upload-list").css("display", "block"); $(".el-upload-list li").css("border", "1px solid #20a0ff"); $("#videoName").text(fileObj.name); } else { alert("请选择文件"); } } /*点击取消*/ function del() { $("#file").val(''); $(".el-upload-list").css("display", "none"); } /*点击提交*/ function sub() { var fileObj = document.getElementById("file").files[0]; // js 获取文件对象 if (fileObj == undefined || fileObj == "") { alert("请选择文件"); return false; } var title = $.trim($("#title").val()); if (title == '') { alert("请填写视频标题"); return false; } var url = "/uploadFile"; // 接收上传文件的后台地址 var form = new FormData(); // FormData 对象 form.append("mf", fileObj); // 文件对象 form.append("title", title); // 标题 xhr = new XMLHttpRequest(); // XMLHttpRequest 对象 xhr.open("post", url, true); // post方式,url为服务器请求地址,true 该参数规定请求是否异步处理。 xhr.onload = uploadComplete; // 请求完成 xhr.onerror = uploadFailed; // 请求失败 xhr.upload.onprogress = progressFunction; //【上传进度调用方法实现】 xhr.upload.onloadstart = function () { //上传开始执行方法 ot = new Date().getTime(); //设置上传开始时间 oloaded = 0; //设置上传开始时,以上传的文件大小为0 }; xhr.send(form); //开始上传,发送form数据 } //上传进度实现方法,上传过程中会频繁调用该方法 function progressFunction(evt) { // event.total是需要传输的总字节,event.loaded是已经传输的字节。如果event.lengthComputable不为真,则event.total等于0 if (evt.lengthComputable) { $(".el-progress--line").css("display", "block"); /*进度条显示进度*/ $(".el-progress-bar__inner").css("width", Math.round(evt.loaded / evt.total * 100) + "%"); $(".el-progress__text").html(Math.round(evt.loaded / evt.total * 100) + "%"); } var time = document.getElementById("time"); var nt = new Date().getTime(); //获取当前时间 var pertime = (nt - ot) / 1000; //计算出上次调用该方法时到现在的时间差,单位为s ot = new Date().getTime(); //重新赋值时间,用于下次计算 var perload = evt.loaded - oloaded; //计算该分段上传的文件大小,单位b oloaded = evt.loaded; //重新赋值已上传文件大小,用以下次计算 //上传速度计算 var speed = perload / pertime; //单位b/s var bspeed = speed; var units = 'b/s'; //单位名称 if (speed / 1024 > 1) { speed = speed / 1024; units = 'k/s'; } if (speed / 1024 > 1) { speed = speed / 1024; units = 'M/s'; } speed = speed.toFixed(1); //剩余时间 var resttime = ((evt.total - evt.loaded) / bspeed).toFixed(1); time.innerHTML = '上传速度:' + speed + units + ',剩余时间:' + resttime + 's'; if (bspeed == 0) time.innerHTML = '上传已取消'; } //上传成功响应 function uploadComplete(evt) { //服务断接收完文件返回的结果 注意返回的字符串要去掉双引号 if (evt.target.responseText) { var responseData = JSON.parse(evt.target.responseText); var mediaUrl = responseData.data.url; alert("上传成功!"); $(".preview").append("<video controls='' autoplay='' name='media'><source src=" + mediaUrl + " type='video/mp4'></video>"); } else { alert("上传失败"); } } //上传失败 function uploadFailed(evt) { alert("上传失败!"); } </script> </body> </html>
2. 后端API层
<?php namespace app\index\controller; use think\Db; use think\Console; use think\Controller; class OssTest extends Controller { // 获取临时访问凭证 public function getTempPlicy() { $oss = \oss\OssFactory::factory("AliOss"); $result = $oss->getRamPolicy(); return $result; } // 简单上传接口 public function uploadFile() { $files = request()->file(); $input['title'] = input('post.title'); $uploadFiled = $files['mf']; // 获取文件信息 $fileInfo = $uploadFiled->getInfo(); $oss = \oss\OssFactory::factory("AliOss"); $result = $oss->upload($uploadFiled, ""); //$width = getimagesize($imgUrl)[0]; //$height = getimagesize($imgUrl)[1]; return $result; } // 分片上传接口 public function multipartUpload() { $files = request()->file(); $input['title'] = input('post.title'); $uploadFiled = $files['mf']; $oss = \oss\OssFactory::factory("AliOss"); $result = $oss->multipartUpload(); return $result; } // 分段Push public function multionPush() { global $_W; global $_GPC; $fileArr = $_FILES['mf']; $title = $_GPC['title']; // 设置预览目录,上传成功的路径 $previewPath = "../shiping/"; $ext = pathinfo($fileArr['name'], PATHINFO_EXTENSION);//获取当前上传文件扩展名 $arrExt = array('3gp','rmvb','flv','wmv','avi','mkv','mp4','mp3','wav',); if(!in_array($ext,$arrExt)) { exit(json_encode(-1,JSON_UNESCAPED_UNICODE)); //视/音频或采用了不合适的扩展名! } else { //文件上传到预览目录 $previewName = 'pre_'.md5(mt_rand(1000,9999)).time().'.'.$ext; //文件重命名 $previewSrc = $previewPath.$previewName; if(move_uploaded_file($fileArr['tmp_name'],$previewSrc)){ // 上传文件操作,上传失败的操作 exit($previewName); } else { //上传成功的失败的操作 exit(json_encode(0,JSON_UNESCAPED_UNICODE)); } } } // 分页push页面 public function multionPushView() { } // 使用ffmpeg开始推流 public function pushLive() { //$command = "ffmpeg -re -i https://resources.huanhuanhuishou.com/global_source/B7689D4E-ED00-B402-EBD9-4F52C3B97247.mp4 -c copy -f flv \"rtmp://hhbusiness.oss-cn-shenzhen.aliyuncs.com/live/test_rtmp_live?playlistName=iphone8.m3u8&OSSAccessKeyId=LTAIp7VhX2YoazcF&Expires=1685586947&Signature=ovAOELN2anMs1Y%2Fzam4BhhKzZYE%3D\""; //exec($command, $output, $return_var); //$result = exec('ipconfig', $output); //var_dump($output[4]);die; } // 创建推流通道 public function putLiveChannel() { $oss = \oss\OssFactory::factory("AliOss"); $result = $oss->putLiveChannel('iphone8'); return $this->jsonData(200, 'ok', $result); } // 获取推流加签地址 public function getSignRtmpUrl() { $oss = \oss\OssFactory::factory("AliOss"); $params = array('params' => array('playlistName' => 'newest.m3u8')); $result = $oss->getSignRtmpUrl($params); return $this->jsonData(200, 'ok', $result); } // 获取推流上传通道历史记录 public function getLiveChannelHistory() { $oss = \oss\OssFactory::factory("AliOss"); //$params = array('params' => array('playlistName' => 'newest.m3u8')); $result = $oss->getLiveChannelHistory(); return $this->jsonData(200, 'ok', $result); } // 查询推流上传信息 public function getLiveChannelInfo() { $oss = \oss\OssFactory::factory("AliOss"); $result = $oss->getLiveChannelInfo(); return $this->jsonData(200, 'ok', $result); } // 查询m3u8的推流上传状态 public function getLiveChannelStatus() { $oss = \oss\OssFactory::factory("AliOss"); $result = $oss->getLiveChannelStatus(); return $this->jsonData(200, 'ok', $result); } }
3. OSS操作层
<?php namespace oss; require_once 'lib/aliyun-oss/autoload.php'; use OSS\OssClient; use OSS\Model\LiveChannelConfig; use OSS\Core\OssException; use OSS\Core\OssUtil; use Mimey\MimeTypes; use AlibabaCloud\SDK\Sts\V20150401\Sts; use Darabonba\OpenApi\Models\Config; use AlibabaCloud\SDK\Sts\V20150401\Models\AssumeRoleRequest; use AlibabaCloud\Tea\Utils\Utils\RuntimeOptions; /** * 阿里云OSS文件传输 */ class AliOss extends OssBase { public $config = []; public function __construct() { $this->config = include 'config/ali_oss_config.php'; } // 获取临时访问凭证 // composer require alibabacloud/sts-20150401 public function getRamPolicy() { $config = $this->config; try { // 填写步骤1创建的RAM用户AccessKey。 $config = new Config([ "accessKeyId" => $config['AccessKeyID'], "accessKeySecret" => $config['AccessKeySecret'] ]); // $config->endpoint = "sts.cn-shenzhen.aliyuncs.com"; $client = new Sts($config); $assumeRoleRequest = new AssumeRoleRequest([ // roleArn填写步骤2获取的角色ARN,例如acs:ram::175708322470****:role/ramtest。 "roleArn" => "acs:ram::175708322470****:role/ramtest", // roleSessionName用于自定义角色会话名称,用来区分不同的令牌,例如填写为sessiontest。 "roleSessionName" => "sessiontest", // durationSeconds用于设置临时访问凭证有效时间单位为秒,最小值为900,最大值以当前角色设定的最大会话时间为准。本示例指定有效时间为3000秒。 "durationSeconds" => 3000, // policy填写自定义权限策略,用于进一步限制STS临时访问凭证的权限。如果不指定Policy,则返回的STS临时访问凭证默认拥有指定角色的所有权限。 // 临时访问凭证最后获得的权限是步骤4设置的角色权限和该Policy设置权限的交集。 //"policy" => "" ]); $runtime = new RuntimeOptions([]); $result = $client->assumeRoleWithOptions($assumeRoleRequest, $runtime); printf("AccessKeyId:" . $result->body->credentials->accessKeyId . PHP_EOL); printf("AccessKeySecret:" . $result->body->credentials->accessKeySecret . PHP_EOL); printf("Expiration:" . $result->body->credentials->expiration . PHP_EOL); printf("SecurityToken:" . $result->body->credentials->securityToken . PHP_EOL); } catch (Exception $e) { printf($e->getMessage() . PHP_EOL); } return $result; } public function upload($file, $dir = "") { $config = $this->config; $fileInfo = $file->getInfo(); // 获取文件扩展名 //$extName = pathinfo($fileInfo['name'], PATHINFO_EXTENSION); if ($dir === "") { $dir = "tmp/"; } $fileName = $dir . $this->getFileName($fileInfo); $res['code'] = 1; try { $ossClient = new OssClient($config['AccessKeyID'], $config['AccessKeySecret'], $config['EndPoint']); $result = $ossClient->uploadFile($config['Bucket'], $fileName, $fileInfo['tmp_name']); $res['data']['url'] = $result['info']['url']; $res['data']['path'] = $fileName; } catch (OssException $e) { $res['code'] = 0; $res['message'] = $e->getMessage(); } return $res; } // 单文件流上传 public function objectUpload($content, $file_name = '') { $config = $this->config; $res['code'] = 1; $res['message'] = ''; try { $ossClient = new OssClient($config['AccessKeyID'], $config['AccessKeySecret'], $config['EndPoint']); $result = $ossClient->putObject($config['Bucket'], $file_name, $content); $res['data']['url'] = $result['info']['url']; $res['data']['path'] = explode("/", $file_name)[1]; } catch (OssException $e) { $res['code'] = 0; $res['message'] = $e->getMessage(); } return $res; } // 分段上传 public function multipartUpload() { $config = $this->config; $ossClient = new OssClient($config['AccessKeyID'], $config['AccessKeySecret'], $config['EndPoint']); // 填写Bucket名称,例如examplebucket。 $bucket = $config['Bucket']; //填写不包含Bucket名称在内的Object完整路径,例如exampledir/exampleobject.txt。 $object = $config['Bucket'] . 'lv_0_20230528160220.mp4'; // 填写本地文件的完整路径。 $uploadFile = 'D:\\lv_0_20230528160220.mp4'; $initOptions = array( OssClient::OSS_HEADERS => array( // 指定该Object被下载时的网页缓存行为。 // 'Cache-Control' => 'no-cache', // 指定该Object被下载时的名称。 // 'Content-Disposition' => 'attachment;filename=oss_download.jpg', // 指定该Object被下载时的内容编码格式。 // 'Content-Encoding' => 'utf-8', // 指定过期时间,单位为毫秒。 // 'Expires' => 150, // 指定初始化分片上传时是否覆盖同名Object。此处设置为true,表示禁止覆盖同名Object。 //'x-oss-forbid-overwrite' => 'true', // 指定上传该Object的每个part时使用的服务器端加密方式。 // 'x-oss-server-side-encryption'=> 'KMS', // 指定Object的加密算法。 // 'x-oss-server-side-data-encryption'=>'SM4', // 指定KMS托管的用户主密钥。 //'x-oss-server-side-encryption-key-id' => '9468da86-3509-4f8d-a61e-6eab1eac****', // 指定Object的存储类型。 // 'x-oss-storage-class' => 'Standard', // 指定Object的对象标签,可同时设置多个标签。 // 'x-oss-tagging' => 'TagA=A&TagB=B', ), ); try { //返回uploadId。uploadId是分片上传事件的唯一标识,您可以根据uploadId发起相关的操作,如取消分片上传、查询分片上传等。 $uploadId = $ossClient->initiateMultipartUpload($bucket, $object, $initOptions); print("initiateMultipartUpload OK" . "\n"); } catch (OssException $e) { printf($e->getMessage() . "\n"); return; } $partSize = 10 * 1024 * 1024; $uploadFileSize = sprintf('%u', filesize($uploadFile)); $pieces = $ossClient->generateMultiuploadParts($uploadFileSize, $partSize); $responseUploadPart = array(); $uploadPosition = 0; $isCheckMd5 = true; foreach ($pieces as $i => $piece) { $fromPos = $uploadPosition + (integer)$piece[$ossClient::OSS_SEEK_TO]; $toPos = (integer)$piece[$ossClient::OSS_LENGTH] + $fromPos - 1; $upOptions = array( // 上传文件。 $ossClient::OSS_FILE_UPLOAD => $uploadFile, // 设置分片号。 $ossClient::OSS_PART_NUM => ($i + 1), // 指定分片上传起始位置。 $ossClient::OSS_SEEK_TO => $fromPos, // 指定文件长度。 $ossClient::OSS_LENGTH => $toPos - $fromPos + 1, // 是否开启MD5校验,true为开启。 $ossClient::OSS_CHECK_MD5 => $isCheckMd5, ); // 开启MD5校验。 if ($isCheckMd5) { $contentMd5 = OssUtil::getMd5SumForFile($uploadFile, $fromPos, $toPos); $upOptions[$ossClient::OSS_CONTENT_MD5] = $contentMd5; } try { // 上传分片。 $responseUploadPart[] = $ossClient->uploadPart($bucket, $object, $uploadId, $upOptions); printf("initiateMultipartUpload, uploadPart - part#{$i} OK\n"); } catch (OssException $e) { printf("initiateMultipartUpload, uploadPart - part#{$i} FAILED\n"); printf($e->getMessage() . "\n"); return; } } // $uploadParts是由每个分片的ETag和分片号(PartNumber)组成的数组。 $uploadParts = array(); foreach ($responseUploadPart as $i => $eTag) { $uploadParts[] = array( 'PartNumber' => ($i + 1), 'ETag' => $eTag, ); } $comOptions['headers'] = array( // 指定完成分片上传时是否覆盖同名Object。此处设置为true,表示禁止覆盖同名Object。 // 'x-oss-forbid-overwrite' => 'true', // 如果指定了x-oss-complete-all:yes,则OSS会列举当前uploadId已上传的所有Part,然后按照PartNumber的序号排序并执行CompleteMultipartUpload操作。 // 'x-oss-complete-all'=> 'yes' ); $res = []; try { // 执行completeMultipartUpload操作时,需要提供所有有效的$uploadParts。OSS收到提交的$uploadParts后,会逐一验证每个分片的有效性。当所有的数据分片验证通过后,OSS将把这些分片组合成一个完整的文件。 $res = $ossClient->completeMultipartUpload($bucket, $object, $uploadId, $uploadParts, $comOptions); printf("Complete Multipart Upload OK\n"); } catch (OssException $e) { printf("Complete Multipart Upload FAILED\n"); printf($e->getMessage() . "\n"); return; } return $res; } // 列举文件 public function listBuckets($bucket = "hhbusiness", $prefix = "", $pagesize = 20, $maker = "") { $config = $this->config; $res['code'] = 1; $res['message'] = ''; $res['data'] = ""; try { $ossClient = new OssClient($config['AccessKeyID'], $config['AccessKeySecret'], $config['EndPoint']); $delimiter = '/'; $nextMarker = ""; //$maxkeys = 1; $options = array( 'delimiter' => $delimiter, 'prefix' => $prefix, 'max-keys' => $pagesize, 'marker' => $maker, ); $result['obj'] = $ossClient->listObjects($bucket, $options); $result['next_marker'] = $result['obj']->getNextMarker(); // 没有值 if ($result['obj']->getIsTruncated() !== "true") { //break; } $res['code'] = 1; $res['data'] = $result; } catch (OssException $e) { $res['code'] = 0; $res['message'] = $e->getMessage(); } return $res; } public function putLiveChannel($liveChannelName) { $ossClientConfig = $this->config; $config = new LiveChannelConfig(array( 'description' => 'live channel test', 'type' => 'HLS', 'fragDuration' => 2, 'fragCount' => 5, 'playListName' => $liveChannelName . '.m3u8' )); $ossClient = new OssClient($ossClientConfig['AccessKeyID'], $ossClientConfig['AccessKeySecret'], $ossClientConfig['EndPoint']); $info = $ossClient->putBucketLiveChannel($ossClientConfig['Bucket'], 'test_rtmp_live', $config); $data['channel_name'] = $info->getName(); $data['channel_description'] = $info->getDescription(); $data['publishurls'] = $info->getPublishUrls(); $data['playurls'] = $info->getPlayUrls(); $data['publishurls_sig'] = $ossClient->signRtmpUrl($ossClientConfig['Bucket'], "test_rtmp_live", 3600, array('params' => array('playlistName' => $liveChannelName . '.m3u8'))); return $data; } public function getSignRtmpUrl($param = []) { $ossClientConfig = $this->config; $ossClient = new OssClient($ossClientConfig['AccessKeyID'], $ossClientConfig['AccessKeySecret'], $ossClientConfig['EndPoint']); $url = $ossClient->signRtmpUrl($ossClientConfig['Bucket'], "test_rtmp_live", 3600, $param); return $url; } public function getLiveChannelHistory() { $ossClientConfig = $this->config; $ossClient = new OssClient($ossClientConfig['AccessKeyID'], $ossClientConfig['AccessKeySecret'], $ossClientConfig['EndPoint']); $history = $ossClient->getLiveChannelHistory($ossClientConfig['Bucket'], "test_rtmp_live"); $list = []; foreach ($history->getLiveRecordList() as $recordList) { $data['startTime'] = $recordList->getStartTime(); $data['endTime'] = $recordList->getEndTime(); $data['remoteAddr'] = $recordList->getRemoteAddr(); $list[] = $data; } return $list; } public function getLiveChannelInfo() { $ossClientConfig = $this->config; $ossClient = new OssClient($ossClientConfig['AccessKeyID'], $ossClientConfig['AccessKeySecret'], $ossClientConfig['EndPoint']); $info = $ossClient->getLiveChannelInfo($ossClientConfig['Bucket'], 'test_rtmp_live'); $data['description'] = $info->getDescription(); $data['status'] = $info->getStatus(); $data['type'] = $info->getType(); $data['fragDuration'] = $info->getFragDuration(); $data['fragCount'] = $info->getFragCount(); $data['playListName'] = $info->getPlayListName(); return $data; } public function getLiveChannelStatus() { $ossClientConfig = $this->config; $ossClient = new OssClient($ossClientConfig['AccessKeyID'], $ossClientConfig['AccessKeySecret'], $ossClientConfig['EndPoint']); $status = $ossClient->getLiveChannelStatus($ossClientConfig['Bucket'], "test_rtmp_live"); $data['status'] = $status->getStatus(); $data['ConnectedTime'] = $status->getConnectedTime(); $data['VideoWidth'] = $status->getVideoWidth(); $data['VideoHeight'] = $status->getVideoHeight(); $data['VideoFrameRate'] = $status->getVideoFrameRate(); $data['VideoBandwidth'] = $status->getVideoBandwidth(); $data['VideoCodec'] = $status->getVideoCodec(); $data['AudioBandwidth'] = $status->getAudioBandwidth(); $data['AudioSampleRate'] = $status->getAudioSampleRate(); $data['AdioCodec'] = $status->getAudioCodec(); return $data; } private function guid($rand = "") { $charid = strtoupper(md5(uniqid(mt_rand(), true) . $rand)); $hyphen = chr(45);// "-" $uuid = substr($charid, 0, 8) . $hyphen . substr($charid, 8, 4) . $hyphen . substr($charid, 12, 4) . $hyphen . substr($charid, 16, 4) . $hyphen . substr($charid, 20, 12); return $uuid; } /** * 获取指定长度的随机字母数字组合的字符串 * @param int $length * @param bool $length * @return string */ private function random($length = 32, $repeat = True) { $pool = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; if ($repeat === True) { $pool = str_repeat($pool, $length); } return substr(str_shuffle($pool), 0, $length); } private function getFileName($file) { $rand = $this->random(); $guid = $this->guid($rand); //$mime_content_type = mime_content_type($file['tmp_name']); $mime_content_type = $extName = pathinfo($file['name'], PATHINFO_EXTENSION);; //$mimes = new MimeTypes(); return $guid . "." . $mime_content_type; } }
4. 推流指令
<?php namespace app\common\command; use think\console\Command; use think\console\Input; use think\console\Output; use think\Db; class PushRtmp extends Command { protected function configure() { $this->setName('push:rtmp')->setDescription('rtmp推流'); } protected function execute(Input $input, Output $output) { $command = "ffmpeg -re -i https://www.neiqiaosu.com/global_source/B7689D4E-ED00-B402-EBD9-4F52C3B97247.mp4 -c copy -f flv \"rtmp://def.oss-cn-shenzhen.aliyuncs.com/live/test_rtmp_live?playlistName=newest.m3u8&OSSAccessKeyId=LTAIp7VhX2YoazcF&Expires=1685606516&Signature=ucEuVujPN2AeHFGbpwF%2BgHqfNPk%3D\""; $result = exec($command, $output, $resultVar); var_dump($output); } }
FFmpeg参数说明
frame= 9753 fps= 30 q=-1.0 Lsize= 129340kB time=00:05:25.09 bitrate=3259.2kbits/s speed= 1x
frame= 9753:表示拍摄的帧数为9753 fps=246:表示每秒拍摄246张照片 q=-1.0:表示视频的质量为-1.0,即质量较差 Lsize= 129340kB:表示视频的大小为129340KB time=00:05:25.09:表示视频的时长为5分25秒9毫秒 bitrate=3259.2kbits/s:表示视频的码率为3259.2KB/s speed=8.21x:表示视频的播放速度为8.21倍
video:116301kB audio:12596kB subtitle:0kB other streams:0kB global headers:0kB muxing overhead: 0.344442%
video:116301kB 视频数据量:116301kB;
audio:12596kB 音频数据量:12596kB;
subtitle:0kB 字幕数据量:0kB;
other streams:0kB 其他数据量:0kB;
global headers:0kB 全局数据头:0kB;
muxing overhead: 0.344442% 多路复用开销:0.344442%
标签:function,上传,oss,data,省钱,result,播放,config From: https://www.cnblogs.com/zerofc/p/17455888.html