1
0

mpegplayer.go 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308
  1. // Copyright 2024 The Ebitengine Authors
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. package main
  15. import (
  16. "fmt"
  17. "image"
  18. "io"
  19. "math"
  20. "sync"
  21. "time"
  22. "github.com/gen2brain/mpeg"
  23. "github.com/hajimehoshi/ebiten/v2"
  24. "github.com/hajimehoshi/ebiten/v2/audio"
  25. )
  26. type mpegPlayer struct {
  27. mpg *mpeg.MPEG
  28. // yCbCrImage is the current frame image in YCbCr format.
  29. // An MPEG frame is stored in this image first.
  30. // Then, this image data is converted to RGB to frameImage.
  31. yCbCrImage *ebiten.Image
  32. // yCbCrBytes is the byte slice to store YCbCr data.
  33. // This includes Y, Cb, Cr, and alpha (always 0xff) data for each pixel.
  34. yCbCrBytes []byte
  35. // yCbCrShader is the shader to convert YCbCr to RGB.
  36. yCbCrShader *ebiten.Shader
  37. // frameImage is the current frame image in RGB format.
  38. frameImage *ebiten.Image
  39. audioPlayer *audio.Player
  40. // These members are used when the video doesn't have an audio stream.
  41. refTime time.Time
  42. src io.ReadCloser
  43. closeOnce sync.Once
  44. m sync.Mutex
  45. }
  46. func newMPEGPlayer(src io.ReadCloser) (*mpegPlayer, error) {
  47. mpg, err := mpeg.New(src)
  48. if err != nil {
  49. return nil, err
  50. }
  51. if mpg.NumVideoStreams() == 0 {
  52. return nil, fmt.Errorf("video: no video streams")
  53. }
  54. if !mpg.HasHeaders() {
  55. return nil, fmt.Errorf("video: missing headers")
  56. }
  57. p := &mpegPlayer{
  58. mpg: mpg,
  59. yCbCrImage: ebiten.NewImage(mpg.Width(), mpg.Height()),
  60. yCbCrBytes: make([]byte, 4*mpg.Width()*mpg.Height()),
  61. frameImage: ebiten.NewImage(mpg.Width(), mpg.Height()),
  62. src: src,
  63. }
  64. s, err := ebiten.NewShader([]byte(`package main
  65. //kage:unit pixels
  66. func Fragment(dstPos vec4, srcPos vec2, color vec4) vec4 {
  67. // For this calculation, see the comment in the standard library color.YCbCrToRGB function.
  68. c := imageSrc0UnsafeAt(srcPos)
  69. return vec4(
  70. c.x + 1.40200 * (c.z-0.5),
  71. c.x - 0.34414 * (c.y-0.5) - 0.71414 * (c.z-0.5),
  72. c.x + 1.77200 * (c.y-0.5),
  73. 1,
  74. )
  75. }
  76. `))
  77. if err != nil {
  78. return nil, err
  79. }
  80. p.yCbCrShader = s
  81. // If the video doesn't have an audio stream, initialization is done.
  82. if mpg.NumAudioStreams() == 0 {
  83. return p, nil
  84. }
  85. // If the video has an audio stream, initialize an audio player.
  86. ctx := audio.CurrentContext()
  87. if ctx == nil {
  88. return nil, fmt.Errorf("video: audio.Context is not initialized")
  89. }
  90. if mpg.Channels() != 2 {
  91. return nil, fmt.Errorf("video: mpeg audio stream must be 2 but was %d", mpg.Channels())
  92. }
  93. if ctx.SampleRate() != mpg.Samplerate() {
  94. return nil, fmt.Errorf("video: mpeg audio stream sample rate %d doesn't match with audio context sample rate %d", mpg.Samplerate(), ctx.SampleRate())
  95. }
  96. mpg.SetAudioFormat(mpeg.AudioF32N)
  97. audioPlayer, err := ctx.NewPlayerF32(&mpegAudio{
  98. audio: mpg.Audio(),
  99. m: &p.m,
  100. })
  101. if err != nil {
  102. return nil, err
  103. }
  104. p.audioPlayer = audioPlayer
  105. return p, nil
  106. }
  107. // updateFrame upadtes the current video frame.
  108. func (p *mpegPlayer) updateFrame() error {
  109. p.m.Lock()
  110. defer p.m.Unlock()
  111. var pos float64
  112. if p.audioPlayer != nil {
  113. pos = p.audioPlayer.Position().Seconds()
  114. } else {
  115. if p.refTime != (time.Time{}) {
  116. pos = time.Since(p.refTime).Seconds()
  117. }
  118. }
  119. video := p.mpg.Video()
  120. if video.HasEnded() {
  121. p.frameImage.Clear()
  122. var err error
  123. p.closeOnce.Do(func() {
  124. fmt.Println("The video has ended.")
  125. if err1 := p.src.Close(); err1 != nil {
  126. err = err1
  127. }
  128. })
  129. return err
  130. }
  131. d := 1 / p.mpg.Framerate()
  132. var mpegFrame *mpeg.Frame
  133. for video.Time()+d <= pos && !video.HasEnded() {
  134. mpegFrame = video.Decode()
  135. }
  136. if mpegFrame == nil {
  137. return nil
  138. }
  139. img := mpegFrame.YCbCr()
  140. if img.SubsampleRatio != image.YCbCrSubsampleRatio420 {
  141. return fmt.Errorf("video: subsample ratio must be 4:2:0")
  142. }
  143. w, h := p.mpg.Width(), p.mpg.Height()
  144. for j := 0; j < h; j++ {
  145. yi := j * img.YStride
  146. ci := (j / 2) * img.CStride
  147. // Create temporary slices to encourage BCE (boundary-checking elimination).
  148. ys := img.Y[yi : yi+w]
  149. cbs := img.Cb[ci : ci+w/2]
  150. crs := img.Cr[ci : ci+w/2]
  151. for i := 0; i < w; i++ {
  152. idx := 4 * (j*w + i)
  153. buf := p.yCbCrBytes[idx : idx+3]
  154. buf[0] = ys[i]
  155. buf[1] = cbs[i/2]
  156. buf[2] = crs[i/2]
  157. // p.yCbCrBytes[3] = 0xff is not needed as the shader ignores this part.
  158. }
  159. }
  160. p.yCbCrImage.WritePixels(p.yCbCrBytes)
  161. // Converting YCbCr to RGB on CPU is slow. Use a shader instead.
  162. op := &ebiten.DrawRectShaderOptions{}
  163. op.Images[0] = p.yCbCrImage
  164. op.Blend = ebiten.BlendCopy
  165. p.frameImage.DrawRectShader(w, h, p.yCbCrShader, op)
  166. return nil
  167. }
  168. // Draw draws the current frame onto the given screen.
  169. func (p *mpegPlayer) Draw(screen *ebiten.Image) error {
  170. if err := p.updateFrame(); err != nil {
  171. return err
  172. }
  173. frame := p.frameImage
  174. sw, sh := screen.Bounds().Dx(), screen.Bounds().Dy()
  175. fw, fh := frame.Bounds().Dx(), frame.Bounds().Dy()
  176. op := ebiten.DrawImageOptions{}
  177. wf, hf := float64(sw)/float64(fw), float64(sh)/float64(fh)
  178. s := wf
  179. if hf < wf {
  180. s = hf
  181. }
  182. op.GeoM.Scale(s, s)
  183. offsetX, offsetY := float64(screen.Bounds().Min.X), float64(screen.Bounds().Min.Y)
  184. op.GeoM.Translate(offsetX+(float64(sw)-float64(fw)*s)/2, offsetY+(float64(sh)-float64(fh)*s)/2)
  185. op.Filter = ebiten.FilterLinear
  186. screen.DrawImage(frame, &op)
  187. return nil
  188. }
  189. // Play starts playing the video.
  190. func (p *mpegPlayer) Play() {
  191. p.m.Lock()
  192. defer p.m.Unlock()
  193. if p.mpg.HasEnded() {
  194. return
  195. }
  196. if p.audioPlayer != nil {
  197. if p.audioPlayer.IsPlaying() {
  198. return
  199. }
  200. // Play refers (*mpegAudio).Read function, where the same mutex is used.
  201. // In order to avoid dead lock, use a different goroutine to start playing.
  202. // This issue happens especially on Windows where goroutines at Play are avoided in Oto (#1768).
  203. // TODO: Remove this hack in the future (ebitengine/oto#235).
  204. go p.audioPlayer.Play()
  205. return
  206. }
  207. if p.refTime != (time.Time{}) {
  208. return
  209. }
  210. p.refTime = time.Now()
  211. }
  212. type mpegAudio struct {
  213. audio *mpeg.Audio
  214. // leftovers is the remaining audio samples of the previous Read call.
  215. leftovers []byte
  216. // m is the mutex shared with the mpegPlayer.
  217. // As *mpeg.MPEG is not concurrent safe, this mutex is necessary.
  218. m *sync.Mutex
  219. }
  220. func (a *mpegAudio) Read(buf []byte) (int, error) {
  221. a.m.Lock()
  222. defer a.m.Unlock()
  223. var readBytes int
  224. if len(a.leftovers) > 0 {
  225. n := copy(buf, a.leftovers)
  226. readBytes += n
  227. buf = buf[n:]
  228. copy(a.leftovers, a.leftovers[n:])
  229. a.leftovers = a.leftovers[:len(a.leftovers)-n]
  230. }
  231. for len(buf) > 0 && !a.audio.HasEnded() {
  232. mpegSamples := a.audio.Decode()
  233. if mpegSamples == nil {
  234. break
  235. }
  236. bs := make([]byte, len(mpegSamples.Interleaved)*4)
  237. for i, s := range mpegSamples.Interleaved {
  238. v := math.Float32bits(s)
  239. bs[4*i] = byte(v)
  240. bs[4*i+1] = byte(v >> 8)
  241. bs[4*i+2] = byte(v >> 16)
  242. bs[4*i+3] = byte(v >> 24)
  243. }
  244. n := copy(buf, bs)
  245. readBytes += n
  246. buf = buf[n:]
  247. if n < len(bs) {
  248. a.leftovers = append(a.leftovers, bs[n:]...)
  249. break
  250. }
  251. }
  252. if a.audio.HasEnded() {
  253. return readBytes, io.EOF
  254. }
  255. return readBytes, nil
  256. }