mirror of
https://github.com/home-assistant/core.git
synced 2025-07-15 09:17:10 +00:00
Use PyAV fork and set hvc1 codec tag for H.265 (#58309)
This commit is contained in:
parent
f6e38fc4e2
commit
35acca1063
@ -2,7 +2,7 @@
|
|||||||
"domain": "stream",
|
"domain": "stream",
|
||||||
"name": "Stream",
|
"name": "Stream",
|
||||||
"documentation": "https://www.home-assistant.io/integrations/stream",
|
"documentation": "https://www.home-assistant.io/integrations/stream",
|
||||||
"requirements": ["av==8.0.3"],
|
"requirements": ["ha-av==8.0.4-rc.1"],
|
||||||
"dependencies": ["http"],
|
"dependencies": ["http"],
|
||||||
"codeowners": ["@hunterjm", "@uvjustin", "@allenporter"],
|
"codeowners": ["@hunterjm", "@uvjustin", "@allenporter"],
|
||||||
"quality_scale": "internal",
|
"quality_scale": "internal",
|
||||||
|
@ -140,6 +140,8 @@ class SegmentBuffer:
|
|||||||
self._output_video_stream = self._av_output.add_stream(
|
self._output_video_stream = self._av_output.add_stream(
|
||||||
template=self._input_video_stream
|
template=self._input_video_stream
|
||||||
)
|
)
|
||||||
|
if self._output_video_stream.name == "hevc":
|
||||||
|
self._output_video_stream.codec_tag = "hvc1"
|
||||||
# Check if audio is requested
|
# Check if audio is requested
|
||||||
self._output_audio_stream = None
|
self._output_audio_stream = None
|
||||||
if self._input_audio_stream and self._input_audio_stream.name in AUDIO_CODECS:
|
if self._input_audio_stream and self._input_audio_stream.name in AUDIO_CODECS:
|
||||||
|
@ -347,9 +347,6 @@ auroranoaa==0.0.2
|
|||||||
# homeassistant.components.aurora_abb_powerone
|
# homeassistant.components.aurora_abb_powerone
|
||||||
aurorapy==0.2.6
|
aurorapy==0.2.6
|
||||||
|
|
||||||
# homeassistant.components.stream
|
|
||||||
av==8.0.3
|
|
||||||
|
|
||||||
# homeassistant.components.avea
|
# homeassistant.components.avea
|
||||||
# avea==1.5.1
|
# avea==1.5.1
|
||||||
|
|
||||||
@ -767,6 +764,9 @@ gstreamer-player==1.1.2
|
|||||||
# homeassistant.components.profiler
|
# homeassistant.components.profiler
|
||||||
guppy3==3.1.0
|
guppy3==3.1.0
|
||||||
|
|
||||||
|
# homeassistant.components.stream
|
||||||
|
ha-av==8.0.4-rc.1
|
||||||
|
|
||||||
# homeassistant.components.ffmpeg
|
# homeassistant.components.ffmpeg
|
||||||
ha-ffmpeg==3.0.2
|
ha-ffmpeg==3.0.2
|
||||||
|
|
||||||
|
@ -238,9 +238,6 @@ auroranoaa==0.0.2
|
|||||||
# homeassistant.components.aurora_abb_powerone
|
# homeassistant.components.aurora_abb_powerone
|
||||||
aurorapy==0.2.6
|
aurorapy==0.2.6
|
||||||
|
|
||||||
# homeassistant.components.stream
|
|
||||||
av==8.0.3
|
|
||||||
|
|
||||||
# homeassistant.components.axis
|
# homeassistant.components.axis
|
||||||
axis==44
|
axis==44
|
||||||
|
|
||||||
@ -466,6 +463,9 @@ growattServer==1.1.0
|
|||||||
# homeassistant.components.profiler
|
# homeassistant.components.profiler
|
||||||
guppy3==3.1.0
|
guppy3==3.1.0
|
||||||
|
|
||||||
|
# homeassistant.components.stream
|
||||||
|
ha-av==8.0.4-rc.1
|
||||||
|
|
||||||
# homeassistant.components.ffmpeg
|
# homeassistant.components.ffmpeg
|
||||||
ha-ffmpeg==3.0.2
|
ha-ffmpeg==3.0.2
|
||||||
|
|
||||||
|
@ -37,7 +37,7 @@ def generate_audio_frame(pcm_mulaw=False):
|
|||||||
return audio_frame
|
return audio_frame
|
||||||
|
|
||||||
|
|
||||||
def generate_h264_video(container_format="mp4", duration=5):
|
def generate_video(encoder, container_format, duration):
|
||||||
"""
|
"""
|
||||||
Generate a test video.
|
Generate a test video.
|
||||||
|
|
||||||
@ -51,7 +51,7 @@ def generate_h264_video(container_format="mp4", duration=5):
|
|||||||
output.name = "test.mov" if container_format == "mov" else "test.mp4"
|
output.name = "test.mov" if container_format == "mov" else "test.mp4"
|
||||||
container = av.open(output, mode="w", format=container_format)
|
container = av.open(output, mode="w", format=container_format)
|
||||||
|
|
||||||
stream = container.add_stream("libx264", rate=fps)
|
stream = container.add_stream(encoder, rate=fps)
|
||||||
stream.width = 480
|
stream.width = 480
|
||||||
stream.height = 320
|
stream.height = 320
|
||||||
stream.pix_fmt = "yuv420p"
|
stream.pix_fmt = "yuv420p"
|
||||||
@ -82,6 +82,16 @@ def generate_h264_video(container_format="mp4", duration=5):
|
|||||||
return output
|
return output
|
||||||
|
|
||||||
|
|
||||||
|
def generate_h264_video(container_format="mp4", duration=5):
|
||||||
|
"""Generate a test video with libx264."""
|
||||||
|
return generate_video("libx264", container_format, duration)
|
||||||
|
|
||||||
|
|
||||||
|
def generate_h265_video(container_format="mp4", duration=5):
|
||||||
|
"""Generate a test video with libx265."""
|
||||||
|
return generate_video("libx265", container_format, duration)
|
||||||
|
|
||||||
|
|
||||||
def remux_with_audio(source, container_format, audio_codec):
|
def remux_with_audio(source, container_format, audio_codec):
|
||||||
"""Remux an existing source with new audio."""
|
"""Remux an existing source with new audio."""
|
||||||
av_source = av.open(source, mode="r")
|
av_source = av.open(source, mode="r")
|
||||||
|
@ -40,7 +40,7 @@ from homeassistant.components.stream.core import StreamSettings
|
|||||||
from homeassistant.components.stream.worker import SegmentBuffer, stream_worker
|
from homeassistant.components.stream.worker import SegmentBuffer, stream_worker
|
||||||
from homeassistant.setup import async_setup_component
|
from homeassistant.setup import async_setup_component
|
||||||
|
|
||||||
from tests.components.stream.common import generate_h264_video
|
from tests.components.stream.common import generate_h264_video, generate_h265_video
|
||||||
from tests.components.stream.test_ll_hls import TEST_PART_DURATION
|
from tests.components.stream.test_ll_hls import TEST_PART_DURATION
|
||||||
|
|
||||||
STREAM_SOURCE = "some-stream-source"
|
STREAM_SOURCE = "some-stream-source"
|
||||||
@ -201,6 +201,9 @@ class FakePyAvBuffer:
|
|||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
return f"FakeAvOutputStream<{template.name}>"
|
return f"FakeAvOutputStream<{template.name}>"
|
||||||
|
|
||||||
|
def name(self) -> str:
|
||||||
|
return "avc1"
|
||||||
|
|
||||||
if template.name == AUDIO_STREAM_FORMAT:
|
if template.name == AUDIO_STREAM_FORMAT:
|
||||||
return FakeAvOutputStream(self.audio_packets)
|
return FakeAvOutputStream(self.audio_packets)
|
||||||
return FakeAvOutputStream(self.video_packets)
|
return FakeAvOutputStream(self.video_packets)
|
||||||
@ -771,3 +774,38 @@ async def test_has_keyframe(hass, record_worker_sync):
|
|||||||
await record_worker_sync.join()
|
await record_worker_sync.join()
|
||||||
|
|
||||||
stream.stop()
|
stream.stop()
|
||||||
|
|
||||||
|
|
||||||
|
async def test_h265_video_is_hvc1(hass, record_worker_sync):
|
||||||
|
"""Test that a h265 video gets muxed as hvc1."""
|
||||||
|
await async_setup_component(
|
||||||
|
hass,
|
||||||
|
"stream",
|
||||||
|
{
|
||||||
|
"stream": {
|
||||||
|
CONF_LL_HLS: True,
|
||||||
|
CONF_SEGMENT_DURATION: SEGMENT_DURATION,
|
||||||
|
CONF_PART_DURATION: TEST_PART_DURATION,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
source = generate_h265_video()
|
||||||
|
stream = create_stream(hass, source, {})
|
||||||
|
|
||||||
|
# use record_worker_sync to grab output segments
|
||||||
|
with patch.object(hass.config, "is_allowed_path", return_value=True):
|
||||||
|
await stream.async_record("/example/path")
|
||||||
|
|
||||||
|
complete_segments = list(await record_worker_sync.get_segments())[:-1]
|
||||||
|
assert len(complete_segments) >= 1
|
||||||
|
|
||||||
|
segment = complete_segments[0]
|
||||||
|
part = segment.parts[0]
|
||||||
|
av_part = av.open(io.BytesIO(segment.init + part.data))
|
||||||
|
assert av_part.streams.video[0].codec_tag == "hvc1"
|
||||||
|
av_part.close()
|
||||||
|
|
||||||
|
await record_worker_sync.join()
|
||||||
|
|
||||||
|
stream.stop()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user