记录一下最近遇到一个需求要把通过Azure媒体服务的直播活动产生的资源,转成可播放的MP4格式并下载到本地。文档参考了Azure Media Service Explore下载工具的源码才实现这个功能。
通过Azure Media Service Explore工具下载直播生成视频文档地址:https://docs.azure.cn/zh-cn/articles/azure-operations-guide/media-services/aog-media-services-qa-download-the-live-generated-video-files
工具下载地址:https://github.com/Azure/Azure-Media-Services-Explorer/releases/tag/v5.2.0.4
以下是实现的代码(C#)
IAzureMediaServicesClient client = CreateMediaServicesClient(subId);
/// <summary>
///
/// </summary>
/// <param name="subscriptionId">订阅ID</param>
/// <returns></returns>
private IAzureMediaServicesClient CreateMediaServicesClient(string subscriptionId)
{
var credentials = GetCredentialsAsync().Result;
return new AzureMediaServicesClient(new Uri(_restApiEndpoint), credentials)
{
SubscriptionId = subscriptionId,
};
}
private async Task<ServiceClientCredentials> GetCredentialsAsync()
{
ClientCredential clientCredential = new ClientCredential(_clientId, _clientSecret);
return await ApplicationTokenProvider.LoginSilentAsync(_tenantId, clientCredential, ActiveDirectoryServiceSettings.AzureChina);
}
/// <summary>
/// 获取资产信息
/// </summary>
/// <param name="ResourceGroup">资源组</param>
/// <param name="AccountName">媒体服务</param>
/// <param name="assetsName">资产名</param>
/// <returns></returns>
public Asset GetAsset(string resourceGroup, string accountName, string assetsName)
{
return client.Assets.Get(resourceGroup, accountName, assetsName);
}
/// <summary>
/// 创建和获取转换编解码器
/// </summary>
/// <param name="resourceGroup">资源组</param>
/// <param name="accountName">媒体服务</param>
/// <param name="transformName">转换名称</param>
/// <returns></returns>
private Transform CreateAndGetCopyCodecTransformIfNeeded(string resourceGroup, string accountName, string transformName)
{
Transform myTransform = null;
bool found = true;
try
{
myTransform = GetTransformAsync(resourceGroup, accountName, transformName);
}
catch
{
found = false;
}
if (!found | myTransform == null)
{
TransformOutput[] outputs;
outputs = new TransformOutput[]
{
new TransformOutput(CustomCopyPreset),
};
myTransform = client.Transforms.CreateOrUpdate(resourceGroup, accountName, transformName, outputs, null);
}
return myTransform;
}
/// <summary>
/// 获取Job信息
/// </summary>
/// <param name="ResourceGroup"></param>
/// <param name="AccountName"></param>
/// <param name="transformName"></param>
/// <param name="jobName"></param>
/// <returns></returns>
public Job GetJob(string ResourceGroup, string AccountName, string transformName, string jobName)
{
return client.Jobs.Get(ResourceGroup, AccountName, transformName, jobName);
}
/// <summary>
/// 创建Job
/// </summary>
/// <param name="resourceGroup">资源组</param>
/// <param name="accountName">媒体服务</param>
/// <param name="transformName"></param>
/// <param name="asset">直播产生的视频资产</param>
/// <param name="jobName">任务名称</param>
/// <param name="start">任务开始时间</param>
/// <param name="end">任务结束时间</param>
/// <param name="outputAsset">转换完成后的资产</param>
/// <param name="assetNameSyntax"></param>
/// <returns></returns>
public JobResult CreateAndSubmitJobs(string resourceGroup, string accountName, string transformName, Asset asset, string jobName = null, ClipTime start = null, ClipTime end = null, Asset outputAsset = null, string assetNameSyntax = null)
{
JobResult result = new JobResult();
string NameconvInputasset = "{Input Asset Name}";
string NameconvTransform = "{Transform Name}";
string NameconvShortUniqueness = "{Uniqueness}";
Transform transform = CreateAndGetCopyCodecTransformIfNeeded(resourceGroup, accountName, transformName);
string jobNameToUse = jobName;
string uniqueness = Guid.NewGuid().ToString().Substring(0, 11).Replace("-", "");
if (jobNameToUse == null)
{
jobNameToUse = $"job-{transform.Name}-{uniqueness}";
}
else if (asset != null) // job name defined but we need to add a uniqueness as there are several assets, so several jobs to submit
{
jobNameToUse += uniqueness;
}
Asset OutputAssetNow = outputAsset;
string OutputAssetNameNow = OutputAssetNow?.Name;
List<JobOutput> jobOutputs = new List<JobOutput>();
if (OutputAssetNow == null)
{
foreach (var outputTrans in transform.Outputs)
{
// output asset name management
if (assetNameSyntax != null)
{
OutputAssetNameNow = assetNameSyntax
.Replace(NameconvInputasset, asset.Name)
.Replace(NameconvTransform, transform.Name)
.Replace(NameconvShortUniqueness, uniqueness);
// example of syntax by default: Constants.NameconvInputasset + "-" + Constants.NameconvTransform + "-" + Constants.NameconvShortUniqueness;
}
else
{
OutputAssetNameNow = $"{asset.Name}-{transform.Name}-{uniqueness}" + ((transform.Outputs.Count > 1) ? "-" + transform.Outputs.IndexOf(outputTrans) : null);
}
// if several outputs, we need to add an index
OutputAssetNameNow += ((transform.Outputs.Count > 1) ? "-" + transform.Outputs.IndexOf(outputTrans) : null);
{
try
{
OutputAssetNow = client.Assets.CreateOrUpdate(
resourceGroup,
accountName,
OutputAssetNameNow,
new Asset()
);
jobOutputs.Add(new JobOutputAsset(OutputAssetNameNow));
}
catch (Exception ex)
{
break;
}
}
}
}
else
{
transform.Outputs.ToList().ForEach(t => jobOutputs.Add(new JobOutputAsset(OutputAssetNameNow)));
}
JobInputAsset jobInput = new JobInputAsset(asset.Name, start: start, end: end);
try
{
Job job =
client.Jobs.Create(
resourceGroup,
accountName,
transform.Name,
jobNameToUse,
new Job
{
Input = jobInput,
Outputs = jobOutputs,
});
result.JobName = jobNameToUse;
result.OutputAssetName = OutputAssetNameNow;
return result;
}
catch (Exception ex)
{
throw ex;
}
}
/// <summary>
/// 下载转换后的MP4资产文件
/// </summary>
/// <param name="resourceGroupName">资源组</param>
/// <param name="accountName">媒体服务</param>
/// <param name="assetName">资产名</param>
/// <param name="resultsFolder">下载到服务器的文件路径</param>
/// <returns></returns>
public MemoryStream DownloadFile(string resourceGroupName, string accountName, string assetName, string resultsFolder)
{
AssetContainerSas assetContainerSas = client.Assets.ListContainerSas(
resourceGroupName,
accountName,
assetName,
permissions: AssetContainerPermission.Read,
expiryTime: DateTime.UtcNow.AddHours(1).ToUniversalTime()
);
Uri containerSasUrl = new Uri(assetContainerSas.AssetContainerSasUrls.FirstOrDefault());
CloudBlobContainer container = new CloudBlobContainer(containerSasUrl);
string directory = resultsFolder;
Directory.CreateDirectory(directory);
var blobs = container.ListBlobsSegmentedAsync(null, true, BlobListingDetails.All, 100, null, null, null).Result;
MemoryStream ms = null;
foreach (var blobItem in blobs.Results)
{
if (blobItem is CloudBlockBlob)
{
CloudBlockBlob blob = blobItem as CloudBlockBlob;
if (blob.Name.ToLower().Contains(".mp4"))
{
string filename = Path.Combine(directory, blob.Name);
if (blob.Name.Contains("/"))
{
var filepath = filename.Split('/')[0];
if (!Directory.Exists(filepath))
Directory.CreateDirectory(filepath);
}
blob.DownloadToFile(filename, FileMode.Create);
if (File.Exists(filename))
{
byte[] buffer = File.ReadAllBytes(filename);
ms = new MemoryStream(buffer, 0, buffer.Length);
File.Delete(filename);
}
}
}
}
return ms;
}
private Transform GetTransformAsync(string resourceGroup, string accountName, string transformName)
{
return client.Transforms.Get(resourceGroup, accountName, transformName);
}
调用方式:
var _selectedAssets = liveSrv.GetAsset(resourceGroup, accountName, model.AssetName);
var result = liveSrv.CreateAndSubmitJobs(resourceGroup, accountName, transformName, _selectedAssets);
var path = Server.MapPath(@"Video");
Stream stream = liveSrv.DownloadFile(resourceGroup, accountName, model.OutputAssetName, path);
byte[] bytes = new byte[(int)stream.Length];
stream.Read(bytes, 0, bytes.Length);
stream.Close();
Response.ContentType = "application/octet-stream;charset=gb2321";
//通知浏览器下载文件而不是打开;对中文名称进行编码
Response.AddHeader("Content-Disposition", "attachment; filename=" + HttpUtility.UrlEncode(model.Title + ".mp4", System.Text.Encoding.UTF8));
Response.BinaryWrite(bytes);
Response.Flush();
Response.End();