Nội dung bài viết
La Kiến Vinh Với Audio Sprite (hay có người gọi là Sound Sprite) Sprite Sheet sẽ hỗ trợ ta phát 1 đoạn âm thanh trong 1 file âm thanh. Để làm được điều này trên các nền tảng khác, ta cũng phải tùy vào việc hỗ trợ của các thư viện đó hoặc tự viết. Trong giới hạn bài này, tôi trình bày về Audio Sprite và mượn HTML5 để giúp các bạn hiểu nhanh vấn đề.

Giới thiệu

Audio Sprite hiểu đơn giản là ta sẽ tổng hợp nhiều âm thanh trong 1 file âm thanh duy nhất. Việc trong sản phẩm cần sử dụng đoạn âm thanh nào thì ta sẽ cho phép phát đoạn âm thanh đó trong file âm thanh, đến hết đoạn thì thư viện âm thanh sẽ ngắt. Tư duy này tương đồng với Image Sprite. Sử dụng Audio Sprite này sẽ giảm bớt được việc request nhiều lần đến Server và giúp tăng khả năng tổ chức lưu trữ âm thanh cho sản phẩm của chúng ta.

Bạn có thể đọc thêm bài Sử Dụng CSS Image Sprites Tối Ưu Tải Trang để dễ hiểu hơn về việc tại sao và lúc nào nên tích hợp nhiều dữ liệu lại thành 1 dữ liệu duy nhất.

Tiền đề bài viết

Thông thường chúng tôi sử dụng C++ để hiện thực Audio Sprite, dẫn đến nó sẽ khó tiếp cận hơn khi diễn đạt nên tôi chưa chia sẻ được lúc đó. Trong lúc cần giải quyết các vấn đề về hạ tầng cho STDIO, tôi phải cùng anh em tìm ra nhiều giải pháp khác nhau để giải quyết và nó dẫn đến việc nảy sinh ra ý tưởng chia sẻ về khái niệm này thông qua HTML5 vì HTML5 sử dụng JavaScript đơn giản và đã hiện thực các tính chất đủ để ta hiện thực được Audio Sprite.

Đối tượng hướng đến

Để hiểu được vấn đề này, bạn cần thiết phải có 1 ít kiến thức về

  • Lập trình với JavaScript.
  • Hoặc nếu bạn không biết về JavaScript thì bạn phải có nền tảng lập trình vững và đã nằm trong nhóm người không phụ thuộc ngôn ngữ lập trình.
  • Hoặc bạn đã từng làm việc với khái niệm Image Sprite (đã từng phát triển sản phẩm games).

Ngoài ra, các bạn khác có thể đọc bài viết ở mức độ tham khảo. Bài viết này thiên về giải pháp, do đó không dành cho người mới tiếp cận lập trình.

Audio Sprite

Audio Sprite là gì?

Giả sử ta có 1 file âm thanh và thay vì phát âm thanh từ đầu cho đến cuối thì ta sẽ phát âm thanh tại 1 thời điểm xác định A đến 1 thời điểm xác định B được lưu trong file âm thanh đó (để dễ hình dung, nó giống như việc ta có thể phát 1 sound-track trong 1 đĩa CD thay vì phải phát nhạc từ đầu đĩa cho đến cuối đĩa).

Audio Sprite có ích lợi gì?

Nếu các file âm thanh ngắn và nhỏ, thì sử dụng Audio Sprite có 1 số lợi ích nhất định.

  • Giảm thiểu tình trạng lượng http-request lên server quá nhiều (môi trường web), xem thêm bài Sử Dụng CSS Image Sprites Tối Ưu Tải Trang để hiểu rõ hơn về tư tưởng tối ưu.
  • Việc quản lý nhiều file nhỏ lẻ độc lập tốn nhiều chi phí và không có vẻ "hướng đối tượng" tốt (nếu các nhóm âm thanh có mục đích liên quan đến âm thanh button, ta có thể dồn nhiều file âm thanh button vào 1 file duy nhất, lúc phát các âm thanh cần thiết liên quan button, ta chỉ việc load file âm thanh button này).
  • Trong 1 chừng mực nhất định, các file âm thanh nhỏ sẽ gây ra hao phí trong lưu trữ 1 ít cho mỗi file, tích hợp lại sẽ giảm được 1 ít về việc hao phí này (bạn có thể tìm hiểu thêm về khái niệm padding).

HTML5 codes thử nghiệm

Cơ bản load và phát âm thanh với HTML5 JavaScript

Phát toàn bộ âm thanh

<!doctype html>
<html>
	<head>
		<title>STDIO Audio Sprite demo</title>
		<script>
			function loadAudio()
			{			
				var audioObj = document.createElement('audio');
				audioObj.setAttribute('id', 'id_stdio_audio');
				
				var sourceObj = document.createElement('source');
				sourceObj.setAttribute('type', 'audio/ogg');
				sourceObj.setAttribute('src',  'sfx_theme_menu_intro.ogg');

				audioObj.appendChild(sourceObj);
				
				document.getElementById('id_stdio').appendChild(audioObj);
				document.getElementById('id_stdio_audio').load();
			}
			
			function playAudio_1()
			{
				var audioObj = document.getElementById('id_stdio_audio');
				audioObj.play();
			}
		</script>
	</head>
	<body id="id_stdio" onload="loadAudio()">
		<button onclick="playAudio_1()">STDIO Play From Start To End</button>
	</body>
</html>

Lưu ý, dòng thứ 21 playAudio_1() phát âm thanh và sẽ phát từ đầu cho đến khi kết thúc file âm thanh.

Phát 1 phần file âm thanh trong khoảng cho trước

Ta viết thêm hàm playAudio_2(start, length) dùng để phát 1 đoạn âm thanh dựa vào 2 thành phần của đối tượng Audio đó là currentTime và sự kiện timeupdate

  • currentTime: dùng để lấy và gán lại thời gian hiện tại cần phát trong file âm thanh.
  • timeupdate: là sự kiện được gọi liên tục khi âm thanh đang được phát. Với sự kiện này ta sẽ liên tục kiểm tra thời gian âm thanh đã phát được bao nhiêu, nếu chạm ngưỡng thời gian mà ta mong muốn ngừng âm thanh thì ta gọi pause âm thanh.

Vậy ta được code hoàn chỉnh với hàm phát 1 phần của âm thanh như sau

function playAudio_2(start, length)
{
	var audioObj = document.getElementById('id_stdio_audio');
	audioObj.currentTime = start;

	audioObj.play();

	audioObj.addEventListener('timeupdate', function() {
		if (this.currentTime > start + length) {
			this.pause();
		}
	});
}

Kết quả mẫu

img_stdio_audio_sprite_example

Download codes mẫu

STDIOAudioDemo.zip

Hiện thực Audio Sprite

Môi trường hiện thực

  • Việc hiện thực này tôi lấy ví dụ trên HTML5.
  • Trình duyệt web Mozilla Firefox 43.0.3.
  • Âm thanh ví dụ trong bài viết lấy từ game SINS với 7 âm thanh cơ bản khi phá hủy 7 loại Sins,
  • Định dạng file âm thanh là ogg (bạn có thể sử dụng trình duyệt web để nghe thử các file âm thanh này hoặc các loại player có hỗ trợ decode ogg).
  • Bạn có thể sử dụng tư duy tương tự với iOS, Android hay bất kỳ hệ thống khác.
  • Lưu ý rằng trong bài viết này, tôi chỉ hỗ trợ demo riêng cho trình duyệt web Mozilla Firefox. Thông thường Chrome hoặc Cốc Cốc vẫn tương thích với ví dụ này.

Nhóm các file âm thanh mong muốn lại thành 1 file

Tôi từng hiện thực game SINS :: sins.stdio.vn trên các thiết bị như Android, iOS, ... và lúc đầu các file âm thanh đều độc lập với nhau. Sau đó tôi có mong đợi mang game SINS lên môi trường web.

Giả sử tôi có 7 file âm thanh độc lập như hình dưới

img_sfx_stdio_sins_destroy

Download 7 file âm thanh độc lập tại đây.

Sử dụng 1 phần mềm đơn giản để nối các file âm thanh này thành 1 file duy nhất - STDIO_7_Sins_UNIQUE_SFX.ogg

img_sfx_sins_destroy

Download file âm thanh sau khi được nhóm lại thành 1 file duy nhất tại đây. Phần mềm tôi sử dụng là Audacity (bạn có thể tìm hiểu để download phiên bản miễn phí).

Trong file âm thanh duy nhất này ta có 7 phân đoạn âm thanh dành cho việc hủy bỏ 1 Sin tính bằng giây.

  • Envy: 0.0s (độ dài 2.5s)
  • Gluttony: 2.7s (độ dài 1.0s)
  • Greed: 3.7s (độ dài 1.1s)
  • Lust: 5.0s (độ dài 2.2s)
  • Pride: 7.2s (độ dài 1.35s)
  • Sloth: 8.5s (độ dài 0.51s)
  • Wrath: 9.1s (độ dài 3.0s)

Từ dữ liệu này ta có Audio Sprite như sau

var AudioSpriteSinsDestroy = {
	'SFX_DESTROY_ENVY'    :[0.0, 2.5],
	'SFX_DESTROY_GLUTTONY':[2.5, 1.0],
	'SFX_DESTROY_GREED'   :[3.6, 1.1],
	'SFX_DESTROY_LUST'    :[5.0, 2.2],
	'SFX_DESTROY_PRIDE'   :[7.2, 1.35],
	'SFX_DESTROY_SLOTH'   :[8.5, 0.51],
	'SFX_DESTROY_WRATH'   :[9.1, 3.0]
};

Tổ chức lại Codes để sử dụng Audio Sprite trên

<!doctype html>
<html>
	<head>
		<title>STDIO Audio Sprite demo</title>
		<script>	
			function loadAudio()
			{			
				var audioObj = document.createElement('audio');
				audioObj.setAttribute('id', 'id_stdio_audio');
				
				var sourceObj = document.createElement('source');
				sourceObj.setAttribute('type', 'audio/ogg');
				sourceObj.setAttribute('src',  'STDIO_7_Sins_UNIQUE_SFX.ogg');

				audioObj.appendChild(sourceObj);
				
				document.getElementById('id_stdio').appendChild(audioObj);
				document.getElementById('id_stdio_audio').load();
			}
			
			var AudioSpriteSinsDestroy = {
					'SFX_DESTROY_ENVY'    :[0.0, 2.5],
					'SFX_DESTROY_GLUTTONY':[2.5, 1.0],
					'SFX_DESTROY_GREED'   :[3.6, 1.1],
					'SFX_DESTROY_LUST'    :[5.0, 2.2],
					'SFX_DESTROY_PRIDE'   :[7.2, 1.35],
					'SFX_DESTROY_SLOTH'   :[8.5, 0.51],
					'SFX_DESTROY_WRATH'   :[9.1, 3.0]
				};
			
			function playAudio(partName)
			{
				var audioObj = document.getElementById('id_stdio_audio');
				audioObj.currentTime = AudioSpriteSinsDestroy[partName][0];
				
				audioObj.play();
				
				audioObj.addEventListener('timeupdate', function() {
					if (this.currentTime > AudioSpriteSinsDestroy[partName][0] + AudioSpriteSinsDestroy[partName][1]) {
						this.pause();
					}
				});
			}
		</script>
	</head>
	<body id="id_stdio" onload="loadAudio()">
		<button onclick="playAudio('SFX_DESTROY_ENVY')">SFX_DESTROY_ENVY</button>
		<button onclick="playAudio('SFX_DESTROY_GLUTTONY')">SFX_DESTROY_GLUTTONY</button>
		<button onclick="playAudio('SFX_DESTROY_GREED')">SFX_DESTROY_GREED</button>
		<button onclick="playAudio('SFX_DESTROY_LUST')">SFX_DESTROY_LUST</button>
		<button onclick="playAudio('SFX_DESTROY_PRIDE')">SFX_DESTROY_PRIDE</button>
		<button onclick="playAudio('SFX_DESTROY_SLOTH')">SFX_DESTROY_SLOTH</button>
		<button onclick="playAudio('SFX_DESTROY_WRATH')">SFX_DESTROY_WRATH</button>		
	</body>
</html>

Download demo hoàn chỉnh

STDIOAudioSpriteFullDemo.zip

THẢO LUẬN
ĐÓNG