公司項目中一部分文件放到了阿裡雲 OSS 上,其中有些音頻文件是 amr 類型的,在後期使用的時候比較麻煩,所以需要轉換成 mp3 的文件,方便以後使用。本來想使用 ffmpeg 處理,但由於文件都存放在阿裡雲 OSS 上,使用 ffmpeg 就需要把文件從遠程下載下來,轉碼之後再重新傳回阿裡雲上,... ...
公司項目中一部分文件放到了阿裡雲 OSS 上,其中有些音頻文件是 amr 類型的,在後期使用的時候比較麻煩,所以需要轉換成 mp3 的文件,方便以後使用。本來想使用 ffmpeg 處理,但由於文件都存放在阿裡雲 OSS 上,使用 ffmpeg 就需要把文件從遠程下載下來,轉碼之後再重新傳回阿裡雲上,還需要使用消息組件進行通知,而且轉碼對伺服器的壓力也會很大。不如直接使用阿裡雲的媒體轉碼服務來的快,阿裡雲只提供了 OSS 的 DotNet 類庫,並沒有提供 MTS 的,所以只能自己參考 API 和其他語言的類庫來開發,還好 MTS 的 API 並不是很複雜,幾次嘗試之後就搞定了。
相關參數需要從阿裡雲的控制台獲取。
阿裡雲媒體轉碼服務類:
/// <summary> /// 阿裡雲媒體轉碼服務助手類。 /// </summary> public class MediaTranscodeHelper { private static readonly Encoding ENCODE_TYPE = Encoding.UTF8; private static readonly string ALGORITHM = "HmacSHA1"; private static readonly string HTTP_METHOD = "GET"; private static readonly string SEPARATOR = "&"; private static readonly string EQUAL = "="; private static readonly string ISO8601_DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ss'Z'"; private static readonly string RegionId = "cn-beijing"; private static readonly string Version = "2014-06-18"; private static readonly string Action = "SubmitJobs"; private static readonly string Format = "JSON"; private static readonly string MtsDomain = "mts.cn-beijing.aliyuncs.com"; private static readonly int MaxRetryNumber = 3; private static readonly bool AutoRetry = true; private static readonly int TimeoutInMilliSeconds = 100000; private static readonly string AccessKeyId = "you AccessKeyId"; private static readonly string AccessKeySecret = "you AccessKeySecret"; private static readonly string PipelineId = "you PipelineId"; /// <summary> /// 提交轉碼任務。 /// </summary> /// <param name="inputFile">輸入文件。</param> /// <returns>輸出文件。</returns> public static string SubmitTranscodeJob(string inputFile, string ossLocation) { string outputJob = string.Empty; return outputJob; } /// <summary> /// 提交轉碼任務。 /// </summary> public async Task<(bool success, string response)> SubmitTranscodeJob() { string SignatureNonce = Guid.NewGuid().ToString(); var paramers = new Dictionary<string, string> { { "Action", Action }, { "Version", "2014-06-18" }, { "AccessKeyId", AccessKeyId }, { "Timestamp", FormatIso8601Date(DateTime.Now) }, { "SignatureMethod", "HMAC-SHA1" }, { "SignatureVersion", "1.0" }, { "SignatureNonce", SignatureNonce }, { "Format", Format }, { "PipelineId", PipelineId }, { "Input", "{\"Bucket\":\"charlesbeijng\",\"Location\":\"oss-cn-beijing\",\"Object\":\"3.amr\"}" }, { "OutputBucket", "charlesbeijng" }, { "OutputLocation", "oss-cn-beijing" }, { "Outputs", " [{\"OutputObject\":\"" + Guid.NewGuid().ToString() + ".mp3\",\"TemplateId\":\"1a94dc364cec44708f00367938a0122f\",\"Location\":\"oss-cn-beijing\",\"WaterMarks\":[{\"InputFile\":{\"Bucket\":\"charlesbeijng\",\"Location\":\"oss-cn-beijing\",\"Object\":\"1.png\"},\"WaterMarkTemplateId\":\"c473de87d0504f44be7ebdac1667ab13\"}]}]" } }; try { string url = GetSignUrl(paramers, AccessKeySecret); int retryTimes = 1; var reply = await HttpGetAsync(url); while (500 <= reply.StatusCode && AutoRetry && retryTimes < MaxRetryNumber) { url = GetSignUrl(paramers, AccessKeySecret); reply = await HttpGetAsync(url); retryTimes++; } if (!string.IsNullOrEmpty(reply.response)) { var res = JsonConvert.DeserializeObject<Dictionary<string, string>>(reply.response); if (res != null && res.ContainsKey("Code") && "OK".Equals(res["Code"])) { return (true, reply.response); } } return (false, reply.response); } catch (Exception ex) { return (false, response: ex.Message); } } /// <summary> /// 同步請求。 /// </summary> /// <param name="url"></param> /// <returns></returns> private static string HttpGet(string url) { HttpWebRequest req = (HttpWebRequest)WebRequest.Create(url); req.Method = HTTP_METHOD; req.KeepAlive = true; req.UserAgent = "idui1"; req.Timeout = TimeoutInMilliSeconds; req.ContentType = "application/x-www-form-urlencoded;charset=utf-8"; HttpWebResponse rsp = null; try { rsp = (HttpWebResponse)req.GetResponse(); } catch (WebException webEx) { if (webEx.Status == WebExceptionStatus.Timeout) { rsp.Close(); } } if (rsp != null) { if (rsp.CharacterSet != null) { Encoding encoding = Encoding.GetEncoding(rsp.CharacterSet); return GetResponseAsString(rsp, encoding); } else { return string.Empty; } } else { return string.Empty; } } /// <summary> /// 非同步請求。 /// </summary> /// <param name="url"></param> /// <returns></returns> private async Task<(int StatusCode, string response)> HttpGetAsync(string url) { HttpClientHandler handler = new HttpClientHandler(); handler.Proxy = null; handler.AutomaticDecompression = DecompressionMethods.GZip; using (var http = new HttpClient(handler)) { http.Timeout = new TimeSpan(TimeSpan.TicksPerMillisecond * TimeoutInMilliSeconds); HttpResponseMessage response = await http.GetAsync(url); return ((int)response.StatusCode, await response.Content.ReadAsStringAsync()); } } /// <summary> /// 把響應流轉換為文本。 /// </summary> /// <param name="rsp">響應流對象</param> /// <param name="encoding">編碼方式</param> /// <returns>響應文本</returns> private static string GetResponseAsString(HttpWebResponse rsp, Encoding encoding) { StringBuilder result = new StringBuilder(); Stream stream = null; StreamReader reader = null; try { // 以字元流的方式讀取HTTP響應 stream = rsp.GetResponseStream(); //rsp.Close(); reader = new StreamReader(stream, encoding); // 每次讀取不大於256個字元,並寫入字元串 char[] buffer = new char[256]; int readBytes = 0; while ((readBytes = reader.Read(buffer, 0, buffer.Length)) > 0) { result.Append(buffer, 0, readBytes); } } catch (WebException webEx) { if (webEx.Status == WebExceptionStatus.Timeout) { result = new StringBuilder(); } } finally { // 釋放資源 if (reader != null) reader.Close(); if (stream != null) stream.Close(); if (rsp != null) rsp.Close(); } return result.ToString(); } /// <summary> /// 處理消息。 /// </summary> /// <param name="message">消息內容。</param> public static void HandlingMessage(string message) { } /// <summary> /// /// </summary> /// <param name="dateTime"></param> /// <returns></returns> private static string FormatIso8601Date(DateTime dateTime) { return dateTime.ToUniversalTime().ToString("yyyy-MM-dd'T'HH:mm:ss'Z'", CultureInfo.CreateSpecificCulture("en-US")); } /// <summary> /// 簽名 /// </summary> public static string SignString(string source, string accessSecret) { using (var algorithm = new HMACSHA1(Encoding.UTF8.GetBytes(accessSecret.ToCharArray()))) { return Convert.ToBase64String(algorithm.ComputeHash(Encoding.UTF8.GetBytes(source.ToCharArray()))); } } private string GetSignUrl(Dictionary<string, string> parameters, string accessSecret) { var imutableMap = new Dictionary<string, string>(parameters) { //{ "Timestamp", FormatIso8601Date(DateTime.Now) }, //{ "SignatureMethod", "HMAC-SHA1" }, //{ "SignatureVersion", "1.0" }, //{ "SignatureNonce", Guid.NewGuid().ToString() }, //{ "Action", Action }, //{ "Version", Version }, //{ "Format", Format }, //{ "RegionId", RegionId } }; IDictionary<string, string> sortedDictionary = new SortedDictionary<string, string>(imutableMap, StringComparer.Ordinal); StringBuilder canonicalizedQueryString = new StringBuilder(); foreach (var p in sortedDictionary) { canonicalizedQueryString.Append("&") .Append(PercentEncode(p.Key)).Append("=") .Append(PercentEncode(p.Value)); } StringBuilder stringToSign = new StringBuilder(); stringToSign.Append(HTTP_METHOD); stringToSign.Append(SEPARATOR); stringToSign.Append(PercentEncode("/")); stringToSign.Append(SEPARATOR); stringToSign.Append(PercentEncode(canonicalizedQueryString.ToString().Substring(1))); string signature = SignString(stringToSign.ToString(), accessSecret + "&"); imutableMap.Add("Signature", signature); return ComposeUrl(MtsDomain, imutableMap); } private static string ComposeUrl(string endpoint, Dictionary<String, String> parameters) { StringBuilder urlBuilder = new StringBuilder(""); urlBuilder.Append("http://").Append(endpoint); if (-1 == urlBuilder.ToString().IndexOf("?")) { urlBuilder.Append("/?"); } string query = ConcatQueryString(parameters); return urlBuilder.Append(query).ToString(); } private static string ConcatQueryString(Dictionary<string, string> parameters) { if (null == parameters) { return null; } StringBuilder sb = new StringBuilder(); foreach (var entry in parameters) { String key = entry.Key; String val = entry.Value; sb.Append(HttpUtility.UrlEncode(key, Encoding.UTF8)); if (val != null) { sb.Append("=").Append(HttpUtility.UrlEncode(val, Encoding.UTF8)); } sb.Append("&"); } int strIndex = sb.Length; if (parameters.Count > 0) sb.Remove(strIndex - 1, 1); return sb.ToString(); } public static string PercentEncode(string value) { StringBuilder stringBuilder = new StringBuilder(); string text = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~"; byte[] bytes = Encoding.GetEncoding("UTF-8").GetBytes(value); foreach (char c in bytes) { if (text.IndexOf(c) >= 0) { stringBuilder.Append(c); } else { stringBuilder.Append("%").Append( string.Format(CultureInfo.InvariantCulture, "{0:X2}", (int)c)); } } return stringBuilder.ToString(); } /// <summary> /// HMAC-SHA1加密演算法 /// </summary> /// <param name="key">密鑰</param> /// <param name="input">要加密的串</param> /// <returns></returns> public static string HmacSha1(string key, string input) { byte[] keyBytes = Encoding.UTF8.GetBytes(key); byte[] inputBytes = Encoding.UTF8.GetBytes(input); HMACSHA1 hmac = new HMACSHA1(keyBytes); byte[] hashBytes = hmac.ComputeHash(inputBytes); return Convert.ToBase64String(hashBytes); } /// <summary> /// AES 演算法加密(ECB模式) 將明文加密,加密後進行base64編碼,返回密文 /// </summary> /// <param name="EncryptStr">明文</param> /// <param name="Key">密鑰</param> /// <returns>加密後base64編碼的密文</returns> public static string Encrypt(string EncryptStr, string Key) { try { //byte[] keyArray = Encoding.UTF8.GetBytes(Key); byte[] keyArray = Convert.FromBase64String(Key); byte[] toEncryptArray = Encoding.UTF8.GetBytes(EncryptStr); RijndaelManaged rDel = new RijndaelManaged { Key = keyArray, Mode = CipherMode.ECB, Padding = PaddingMode.PKCS7 }; ICryptoTransform cTransform = rDel.CreateEncryptor(); byte[] resultArray = cTransform.TransformFinalBlock(toEncryptArray, 0, toEncryptArray.Length); return Convert.ToBase64String(resultArray, 0, resultArray.Length); } catch (Exception) { return null; } } public static string Decrypt(string toDecrypt, string key) { byte[] keyArray = Convert.FromBase64String(key); // 將 TestGenAESByteKey 類輸出的字元串轉為 byte 數組 byte[] toEncryptArray = Convert.FromBase64String(toDecrypt); RijndaelManaged rDel = new RijndaelManaged { Key = keyArray, Mode = CipherMode.ECB, // 必須設置為 ECB Padding = PaddingMode.PKCS7 // 必須設置為 PKCS7 }; ICryptoTransform cTransform = rDel.CreateDecryptor(); byte[] resultArray = cTransform.TransformFinalBlock(toEncryptArray, 0, toEncryptArray.Length); return Encoding.UTF8.GetString(resultArray); } private static string BuildCanonicalizedQueryString(Dictionary<string, string> parameters) { // 對參數進行排序 List<string> sortedKeys = new List<string>(parameters.Keys); sortedKeys.Sort(); StringBuilder temp = new StringBuilder(); foreach (var key in sortedKeys) { // 此處需要對 key 和 value 進行編碼 string value = parameters[key]; temp.Append(SEPARATOR).Append(PercentEncode(key)).Append(EQUAL).Append(PercentEncode(value)); } return temp.ToString().Substring(1); } private static string BuildRequestURL(string signature, Dictionary<string, string> parameters) { // 生成請求 URL StringBuilder temp = new StringBuilder("mts.cn-beijing.aliyuncs.com"); temp.Append(HttpUtility.UrlEncode("Signature", ENCODE_TYPE)).Append("=").Append(signature); foreach (var item in parameters) { temp.Append("&").Append(PercentEncode(item.Key)).Append("=").Append(PercentEncode(item.Value)); } return temp.ToString(); } }
使用的時候直接調用 SubmitTranscodeJob 方法就可以了。