Recherche avancée

Médias (0)

Mot : - Tags -/logo

Aucun média correspondant à vos critères n’est disponible sur le site.

Autres articles (42)

  • Gestion générale des documents

    13 mai 2011, par

    MédiaSPIP ne modifie jamais le document original mis en ligne.
    Pour chaque document mis en ligne il effectue deux opérations successives : la création d’une version supplémentaire qui peut être facilement consultée en ligne tout en laissant l’original téléchargeable dans le cas où le document original ne peut être lu dans un navigateur Internet ; la récupération des métadonnées du document original pour illustrer textuellement le fichier ;
    Les tableaux ci-dessous expliquent ce que peut faire MédiaSPIP (...)

  • Des sites réalisés avec MediaSPIP

    2 mai 2011, par

    Cette page présente quelques-uns des sites fonctionnant sous MediaSPIP.
    Vous pouvez bien entendu ajouter le votre grâce au formulaire en bas de page.

  • HTML5 audio and video support

    13 avril 2011, par

    MediaSPIP uses HTML5 video and audio tags to play multimedia files, taking advantage of the latest W3C innovations supported by modern browsers.
    The MediaSPIP player used has been created specifically for MediaSPIP and can be easily adapted to fit in with a specific theme.
    For older browsers the Flowplayer flash fallback is used.
    MediaSPIP allows for media playback on major mobile platforms with the above (...)

Sur d’autres sites (6218)

  • FFmpeg-wasm unpredictable erros with next js

    21 juillet, par hjtomi

    I use ffmpeg-wasm in next-js.
Whenever I execute an ffmpeg command it has a chance that it throws a runtime error saying memory access out of bounds. In this case I created a page that has an file selection html element and a button that converts each image to jpeg format with the following command :

    


    await ffmpeg.exec(['-i', originalFileName, '-q:v', '5', convertedFileName]);

    


    I have tried the same process with different images, extensions, sizes, restarting the application, restarting the computer, different browsers, mobile/desktop and sometimes it works, sometimes it doesnt. I feel like there is something that is out of my control. Maybe something that has to do with webassembly itself.

    


    In each runtime it has a 50-50 chance that ffmpeg will work or not.

    


    I have tried changing the target extension to different types.

    


    I have tried changing the ffmpeg command entirely to make it do something else.

    


    Writing the files to the virutal file system works as expected.

    


    I am on windows 10 using next-js version 15.3.5 and ffmpeg version 0.12.10

    


    &#x27;use client&#x27;;&#xA;&#xA;import * as React from &#x27;react&#x27;;&#xA;import { FFmpeg } from &#x27;@ffmpeg/ffmpeg&#x27;; // Import FFmpeg&#xA;import { fetchFile, toBlobURL } from &#x27;@ffmpeg/util&#x27;; // Import fetchFile&#xA;&#xA;export default function Page() {&#xA;  const [files, setFiles] = React.useState([]);&#xA;  const [error, setError] = React.useState<string null="null">(null);&#xA;&#xA;  // FFmpeg related states and ref&#xA;  const ffmpegRef = React.useRef<ffmpeg null="null">(null);&#xA;  const [ffmpegLoaded, setFFmpegLoaded] = React.useState(false);&#xA;  const [isConvertingImages, setIsConvertingImages] = React.useState(false);&#xA;  const [videoGenerationProgress, setVideoGenerationProgress] = React.useState<string>(&#x27;&#x27;);&#xA;&#xA;  // --- Load FFmpeg on component mount ---&#xA;  React.useEffect(() => {&#xA;    const loadFFmpeg = async () => {&#xA;      const baseURL = "https://unpkg.com/@ffmpeg/core@0.12.10/dist/umd";&#xA;      try {&#xA;        const ffmpeg = new FFmpeg();&#xA;        // Set up logging for FFmpeg progress&#xA;        ffmpeg.on(&#x27;log&#x27;, ({ message }) => {&#xA;          if (message.includes(&#x27;frame=&#x27;)) {&#xA;            setVideoGenerationProgress(message);&#xA;          }&#xA;        });&#xA;        await ffmpeg.load({&#xA;              coreURL: await toBlobURL(`${baseURL}/ffmpeg-core.js`, "text/javascript"),&#xA;              wasmURL: await toBlobURL(`${baseURL}/ffmpeg-core.wasm`, "application/wasm"),&#xA;            });&#xA;        ffmpegRef.current = ffmpeg;&#xA;        setFFmpegLoaded(true);&#xA;        console.log(&#x27;FFmpeg loaded successfully!&#x27;);&#xA;      } catch (err) {&#xA;        console.error(&#x27;Failed to load FFmpeg:&#x27;, err);&#xA;        setError(&#x27;Failed to load video processing tools.&#x27;);&#xA;      }&#xA;    };&#xA;&#xA;    loadFFmpeg();&#xA;  }, []);&#xA;&#xA;  // --- Internal file selection logic ---&#xA;  const handleFileChange = (e: React.ChangeEvent<htmlinputelement>) => {&#xA;    if (e.target.files &amp;&amp; e.target.files.length > 0) {&#xA;      const newFiles = Array.from(e.target.files).filter(file => file.type.startsWith(&#x27;image/&#x27;)); // Only accept images&#xA;      setFiles(newFiles);&#xA;      setError(null);&#xA;    }&#xA;  };&#xA;&#xA;  // --- Handle Image conversion ---&#xA;  const handleConvertImages = async () => {&#xA;    if (!ffmpegLoaded || !ffmpegRef.current) {&#xA;      setError(&#x27;FFmpeg is not loaded yet. Please wait.&#x27;);&#xA;      return;&#xA;    }&#xA;    if (files.length === 0) {&#xA;      setError(&#x27;Please select images first to generate a video.&#x27;);&#xA;      return;&#xA;    }&#xA;&#xA;    setIsConvertingImages(true);&#xA;    setError(null);&#xA;    setVideoGenerationProgress(&#x27;&#x27;);&#xA;&#xA;    try {&#xA;      const ffmpeg = ffmpegRef.current;&#xA;      const targetExtension = &#x27;jpeg&#x27;; // &lt;--- Define your target extension here (e.g., &#x27;png&#x27;, &#x27;webp&#x27;)&#xA;      const convertedImageNames: string[] = [];&#xA;&#xA;      // Convert all uploaded images to the target format&#xA;      for (let i = 0; i &lt; files.length; i&#x2B;&#x2B;) {&#xA;          const file = files[i];&#xA;          // Give the original file a unique name in FFmpeg&#x27;s VFS&#xA;          const originalFileName = `original_image_${String(i).padStart(3, &#x27;0&#x27;)}.${file.name.split(&#x27;.&#x27;).pop()}`;&#xA;          // Define the output filename with the target extension&#xA;          const convertedFileName = `converted_image_${String(i).padStart(3, &#x27;0&#x27;)}.${targetExtension}`;&#xA;          convertedImageNames.push(convertedFileName);&#xA;&#xA;          // Write the original file data to FFmpeg&#x27;s virtual file system&#xA;          await ffmpeg.writeFile(`${originalFileName}`, await fetchFile(file));&#xA;          console.log(`Wrote original ${originalFileName} to FFmpeg FS`);&#xA;&#xA;          setVideoGenerationProgress(`Converting ${file.name} to ${targetExtension.toUpperCase()}...`);&#xA;&#xA;          await ffmpeg.exec([&#x27;-i&#x27;, originalFileName, &#x27;-q:v&#x27;, &#x27;5&#x27;, convertedFileName]);     //    &lt;------&#xA;&#xA;          console.log(`Converted ${originalFileName} to ${convertedFileName}`);&#xA;&#xA;          // Delete the original file from VFS to free up memory&#xA;          await ffmpeg.deleteFile(originalFileName);&#xA;          console.log(`Deleted original ${originalFileName} from FFmpeg FS`);&#xA;      }&#xA;&#xA;      setFiles([]);&#xA;&#xA;      setVideoGenerationProgress(`All images converted to ${targetExtension.toUpperCase()}.`);&#xA;    } catch (err) {&#xA;      console.error(&#x27;Error converting images:&#x27;, err);&#xA;      setError(`Failed to convert images: ${err instanceof Error ? err.message : String(err)}`);&#xA;    } finally {&#xA;      setIsConvertingImages(false);&#xA;    }&#xA;  };&#xA;&#xA;  return (&#xA;    <div classname="min-h-screen bg-gray-100 flex items-center justify-center p-4 relative overflow-hidden">&#xA;      <div classname="bg-white p-8 rounded-lg shadow-xl w-full max-w-md z-10 relative"> {/* Ensure content is above overlay */}&#xA;        <h2 classname="text-2xl font-semibold text-gray-800 mb-6 text-center">Select your images</h2>&#xA;&#xA;        {/* Regular File Input Area (still needed for click-to-select) */}&#xA;        > document.getElementById(&#x27;file-input&#x27;)?.click()}&#xA;        >&#xA;        &#xA;          &#xA;          <svg classname="mx-auto h-12 w-12 text-gray-400" fill="none" viewbox="0 0 24 24" stroke="currentColor">&#xA;            <path strokelinecap="round" strokelinejoin="round" strokewidth="2" d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12"></path>&#xA;          </svg>&#xA;          <p classname="mt-2 text-sm text-gray-600">&#xA;            <span classname="font-medium text-blue-600">Click to select</span>&#xA;          </p>&#xA;          <p classname="text-xs text-gray-500">Supports multiple formats</p>&#xA;        </div>&#xA;&#xA;        {/* Selected Files Display */}&#xA;        {files.length > 0 &amp;&amp; (&#xA;          <div data-testid="selected-files-display" classname="selected-files-display mt-4 p-3 bg-blue-50 border border-blue-200 rounded-md text-sm text-blue-800">&#xA;            <p classname="font-semibold mb-2">Selected Files ({files.length}):</p>&#xA;            <ul classname="max-h-40 overflow-y-auto">&#xA;              {files.map((file) => (&#xA;                <li key="{file.name}" classname="flex items-center justify-between py-1">&#xA;                  <span>{file.name}</span>&#xA;                </li>&#xA;              ))}&#xA;            </ul>&#xA;          </div>&#xA;        )}&#xA;&#xA;        {/* Error Message */}&#xA;        {error &amp;&amp; (&#xA;          <div classname="mt-4 p-3 bg-red-50 border border-red-200 rounded-md text-sm text-red-800">&#xA;            {error}&#xA;          </div>&#xA;        )}&#xA;&#xA;        {/* Image conversion Progress/Status */}&#xA;        {isConvertingImages &amp;&amp; (&#xA;          <div classname="mt-4 p-3 bg-purple-50 border border-purple-200 rounded-md text-sm text-purple-800 text-center">&#xA;            <p classname="font-semibold">Converting images</p>&#xA;            <p classname="text-xs mt-1">{videoGenerationProgress}</p>&#xA;          </div>&#xA;        )}&#xA;        {!ffmpegLoaded &amp;&amp; (&#xA;          <div classname="mt-4 p-3 bg-yellow-50 border border-yellow-200 rounded-md text-sm text-yellow-800 text-center">&#xA;            Loading video tools (FFmpeg)... Please wait.&#xA;          </div>&#xA;        )}&#xA;&#xA;        {/* Convert images button */}&#xA;        &#xA;          Convert images&#xA;        &#xA;      </div>&#xA;    &#xA;  );&#xA;}&#xA;</htmlinputelement></string></ffmpeg></string>

    &#xA;

    The next js page above

    &#xA;

  • Merged Video Contains Inverted Clips After First Video Ends

    3 février, par Nikunj Agrawal

    I am working on a Flutter application that merges multiple videos using ffmpeg_kit_flutter . However, after merging, I notice that the second video (and any subsequent ones) appear inverted or rotated in the final output.

    &#xA;

    Issue Details :

    &#xA;

      &#xA;
    1. The first video appears normal.
    2. &#xA;

    3. The videos can be recorded using both front and back cameras.
    4. &#xA;

    5. The second (and later) videos are flipped or rotated upside down.
    6. &#xA;

    7. This happens after merging using ffmpeg_kit_flutter.
    8. &#xA;

    &#xA;

    Question :&#xA;How can I correctly merge multiple videos in Flutter without rotation issues ? Is there a way to normalize video orientation before merging using ffmpeg_kit_flutter ?

    &#xA;

    Any help would be appreciated ! 🚀

    &#xA;

    Code :

    &#xA;

    import &#x27;dart:io&#x27;;&#xA;import &#x27;dart:math&#x27;;&#xA;&#xA;import &#x27;package:camera/camera.dart&#x27;;&#xA;import &#x27;package:ffmpeg_kit_flutter/ffmpeg_kit.dart&#x27;;&#xA;import &#x27;package:ffmpeg_kit_flutter/return_code.dart&#x27;;&#xA;import &#x27;package:flutter/material.dart&#x27;;&#xA;import &#x27;package:path_provider/path_provider.dart&#x27;;&#xA;import &#x27;package:permission_handler/permission_handler.dart&#x27;;&#xA;import &#x27;package:record/record.dart&#x27;;&#xA;import &#x27;package:videotest/video_player.dart&#x27;;&#xA;&#xA;class MergeVideoRecording extends StatefulWidget {&#xA;  const MergeVideoRecording({super.key});&#xA;&#xA;  @override&#xA;  State<mergevideorecording> createState() => _MergeVideoRecordingState();&#xA;}&#xA;&#xA;class _MergeVideoRecordingState extends State<mergevideorecording> {&#xA;  CameraController? _cameraController;&#xA;  final AudioRecorder _audioRecorder = AudioRecorder();&#xA;&#xA;  bool _isRecording = false;&#xA;  String? _videoPath;&#xA;  String? _audioPath;&#xA;  List<cameradescription> _cameras = [];&#xA;  int _currentCameraIndex = 0;&#xA;  final List<string> _recordedVideos = [];&#xA;&#xA;  @override&#xA;  Widget build(BuildContext context) {&#xA;    return Scaffold(&#xA;      body: Column(&#xA;        mainAxisAlignment: MainAxisAlignment.center,&#xA;        children: [&#xA;          _cameraController != null &amp;&amp; _cameraController!.value.isInitialized&#xA;              ? SizedBox(&#xA;                  width: MediaQuery.of(context).size.width * 0.4,&#xA;                  height: MediaQuery.of(context).size.height * 0.3,&#xA;                  child: Stack(&#xA;                    children: [&#xA;                      ClipRRect(&#xA;                        borderRadius: BorderRadius.circular(16),&#xA;                        child: SizedBox(&#xA;                          width: MediaQuery.of(context).size.width * 0.4,&#xA;                          height: MediaQuery.of(context).size.height * 0.3,&#xA;                          child: Transform(&#xA;                            alignment: Alignment.center,&#xA;                            transform:&#xA;                                _cameras[_currentCameraIndex].lensDirection ==&#xA;                                        CameraLensDirection.front&#xA;                                    ? Matrix4.rotationY(pi)&#xA;                                    : Matrix4.identity(),&#xA;                            child: CameraPreview(_cameraController!),&#xA;                          ),&#xA;                        ),&#xA;                      ),&#xA;                      Align(&#xA;                        alignment: Alignment.topRight,&#xA;                        child: InkWell(&#xA;                          onTap: _switchCamera,&#xA;                          child: const Padding(&#xA;                            padding: EdgeInsets.all(8.0),&#xA;                            child: CircleAvatar(&#xA;                              radius: 18,&#xA;                              backgroundColor: Colors.white,&#xA;                              child: Icon(&#xA;                                Icons.flip_camera_android,&#xA;                                color: Colors.black,&#xA;                              ),&#xA;                            ),&#xA;                          ),&#xA;                        ),&#xA;                      ),&#xA;                    ],&#xA;                  ),&#xA;                )&#xA;              : const CircularProgressIndicator(),&#xA;          const SizedBox(height: 16),&#xA;          Row(&#xA;            mainAxisAlignment: MainAxisAlignment.center,&#xA;            children: [&#xA;              FloatingActionButton(&#xA;                heroTag: &#x27;record_button&#x27;,&#xA;                onPressed: _toggleRecording,&#xA;                child: Icon(&#xA;                  _isRecording ? Icons.stop : Icons.video_camera_back,&#xA;                ),&#xA;              ),&#xA;              const SizedBox(&#xA;                width: 50,&#xA;              ),&#xA;              FloatingActionButton(&#xA;                heroTag: &#x27;merge_button&#x27;,&#xA;                onPressed: _mergeVideos,&#xA;                child: const Icon(&#xA;                  Icons.merge,&#xA;                ),&#xA;              ),&#xA;            ],&#xA;          ),&#xA;          if (!_isRecording)&#xA;            ListView.builder(&#xA;              shrinkWrap: true,&#xA;              itemCount: _recordedVideos.length,&#xA;              itemBuilder: (context, index) => InkWell(&#xA;                onTap: () {&#xA;                  Navigator.push(&#xA;                    context,&#xA;                    MaterialPageRoute(&#xA;                      builder: (context) => VideoPlayerScreen(&#xA;                        videoPath: _recordedVideos[index],&#xA;                      ),&#xA;                    ),&#xA;                  );&#xA;                },&#xA;                child: ListTile(&#xA;                  title: Text(&#x27;Video ${index &#x2B; 1}&#x27;),&#xA;                  subtitle: Text(&#x27;Path ${_recordedVideos[index]}&#x27;),&#xA;                  trailing: const Icon(Icons.play_arrow),&#xA;                ),&#xA;              ),&#xA;            ),&#xA;        ],&#xA;      ),&#xA;    );&#xA;  }&#xA;&#xA;  @override&#xA;  void dispose() {&#xA;    _cameraController?.dispose();&#xA;    _audioRecorder.dispose();&#xA;    super.dispose();&#xA;  }&#xA;&#xA;  @override&#xA;  void initState() {&#xA;    super.initState();&#xA;    _initializeDevices();&#xA;  }&#xA;&#xA;  Future<void> _initializeCameraController(CameraDescription camera) async {&#xA;    _cameraController = CameraController(&#xA;      camera,&#xA;      ResolutionPreset.high,&#xA;      enableAudio: true,&#xA;      imageFormatGroup: ImageFormatGroup.yuv420, // Add this line&#xA;    );&#xA;&#xA;    await _cameraController!.initialize();&#xA;    await _cameraController!.setExposureMode(ExposureMode.auto);&#xA;    await _cameraController!.setFocusMode(FocusMode.auto);&#xA;    setState(() {});&#xA;  }&#xA;&#xA;  Future<void> _initializeDevices() async {&#xA;    final cameraStatus = await Permission.camera.request();&#xA;    final micStatus = await Permission.microphone.request();&#xA;&#xA;    if (!cameraStatus.isGranted || !micStatus.isGranted) {&#xA;      _showError(&#x27;Camera and microphone permissions required&#x27;);&#xA;      return;&#xA;    }&#xA;&#xA;    _cameras = await availableCameras();&#xA;    if (_cameras.isNotEmpty) {&#xA;      final frontCameraIndex = _cameras.indexWhere(&#xA;          (camera) => camera.lensDirection == CameraLensDirection.front);&#xA;      _currentCameraIndex = frontCameraIndex != -1 ? frontCameraIndex : 0;&#xA;      await _initializeCameraController(_cameras[_currentCameraIndex]);&#xA;    }&#xA;  }&#xA;&#xA;  // Merge video&#xA;  Future<void> _mergeVideos() async {&#xA;    if (_recordedVideos.isEmpty) {&#xA;      _showError(&#x27;No videos to merge&#x27;);&#xA;      return;&#xA;    }&#xA;&#xA;    try {&#xA;      // Debug logging&#xA;      print(&#x27;Starting merge process&#x27;);&#xA;      print(&#x27;Number of videos to merge: ${_recordedVideos.length}&#x27;);&#xA;      for (var i = 0; i &lt; _recordedVideos.length; i&#x2B;&#x2B;) {&#xA;        final file = File(_recordedVideos[i]);&#xA;        final exists = await file.exists();&#xA;        final size = exists ? await file.length() : 0;&#xA;        print(&#x27;Video $i: ${_recordedVideos[i]}&#x27;);&#xA;        print(&#x27;Exists: $exists, Size: $size bytes&#x27;);&#xA;      }&#xA;&#xA;      final Directory appDir = await getApplicationDocumentsDirectory();&#xA;      final String outputPath =&#xA;          &#x27;${appDir.path}/merged_${DateTime.now().millisecondsSinceEpoch}.mp4&#x27;;&#xA;      final String listFilePath = &#x27;${appDir.path}/list.txt&#x27;;&#xA;&#xA;      print(&#x27;Output path: $outputPath&#x27;);&#xA;      print(&#x27;List file path: $listFilePath&#x27;);&#xA;&#xA;      // Create and verify list file&#xA;      final listFile = File(listFilePath);&#xA;      final fileContent = _recordedVideos&#xA;          .map((path) => "file &#x27;${path.replaceAll("&#x27;", "&#x27;\\&#x27;&#x27;")}&#x27;")&#xA;          .join(&#x27;\n&#x27;);&#xA;      await listFile.writeAsString(fileContent);&#xA;&#xA;      print(&#x27;List file content:&#x27;);&#xA;      print(await listFile.readAsString());&#xA;&#xA;      // Simpler FFmpeg command for testing&#xA;      final command = &#x27;&#x27;&#x27;&#xA;      -f concat&#xA;      -safe 0&#xA;      -i "$listFilePath"&#xA;      -c copy&#xA;      -y&#xA;      "$outputPath"&#xA;    &#x27;&#x27;&#x27;&#xA;          .trim()&#xA;          .replaceAll(&#x27;\n&#x27;, &#x27; &#x27;);&#xA;&#xA;      print(&#x27;Executing FFmpeg command: $command&#x27;);&#xA;&#xA;      final session = await FFmpegKit.execute(command);&#xA;      final returnCode = await session.getReturnCode();&#xA;      final logs = await session.getAllLogsAsString();&#xA;      final failStackTrace = await session.getFailStackTrace();&#xA;&#xA;      print(&#x27;FFmpeg return code: ${returnCode?.getValue() ?? "null"}&#x27;);&#xA;      print(&#x27;FFmpeg logs: $logs&#x27;);&#xA;      if (failStackTrace != null) {&#xA;        print(&#x27;FFmpeg fail stack trace: $failStackTrace&#x27;);&#xA;      }&#xA;&#xA;      if (ReturnCode.isSuccess(returnCode)) {&#xA;        final outputFile = File(outputPath);&#xA;        final outputExists = await outputFile.exists();&#xA;        final outputSize = outputExists ? await outputFile.length() : 0;&#xA;&#xA;        print(&#x27;Output file exists: $outputExists&#x27;);&#xA;        print(&#x27;Output file size: $outputSize bytes&#x27;);&#xA;&#xA;        if (outputExists &amp;&amp; outputSize > 0) {&#xA;          setState(() => _recordedVideos.add(outputPath));&#xA;          _showSuccess(&#x27;Videos merged successfully&#x27;);&#xA;        } else {&#xA;          _showError(&#x27;Merged file is empty or not created&#x27;);&#xA;        }&#xA;      } else {&#xA;        _showError(&#x27;Failed to merge videos. Check logs for details.&#x27;);&#xA;      }&#xA;&#xA;      // Clean up&#xA;      try {&#xA;        await listFile.delete();&#xA;        print(&#x27;List file cleaned up successfully&#x27;);&#xA;      } catch (e) {&#xA;        print(&#x27;Failed to delete list file: $e&#x27;);&#xA;      }&#xA;    } catch (e, s) {&#xA;      print(&#x27;Error during merge: $e&#x27;);&#xA;      print(&#x27;Stack trace: $s&#x27;);&#xA;      _showError(&#x27;Error merging videos: ${e.toString()}&#x27;);&#xA;    }&#xA;  }&#xA;&#xA;  void _showError(String message) {&#xA;    ScaffoldMessenger.of(context).showSnackBar(&#xA;      SnackBar(content: Text(message), backgroundColor: Colors.red),&#xA;    );&#xA;  }&#xA;&#xA;  void _showSuccess(String message) {&#xA;    ScaffoldMessenger.of(context).showSnackBar(&#xA;      SnackBar(content: Text(message), backgroundColor: Colors.green),&#xA;    );&#xA;  }&#xA;&#xA;  Future<void> _startAudioRecording() async {&#xA;    try {&#xA;      final Directory tempDir = await getTemporaryDirectory();&#xA;      final audioPath = &#x27;${tempDir.path}/recording.wav&#x27;;&#xA;      await _audioRecorder.start(const RecordConfig(), path: audioPath);&#xA;      setState(() => _isRecording = true);&#xA;    } catch (e) {&#xA;      _showError(&#x27;Recording start error: $e&#x27;);&#xA;    }&#xA;  }&#xA;&#xA;  Future<void> _startVideoRecording() async {&#xA;    try {&#xA;      await _cameraController!.startVideoRecording();&#xA;      setState(() => _isRecording = true);&#xA;    } catch (e) {&#xA;      _showError(&#x27;Recording start error: $e&#x27;);&#xA;    }&#xA;  }&#xA;&#xA;  Future<void> _stopAndSaveAudioRecording() async {&#xA;    _audioPath = await _audioRecorder.stop();&#xA;    if (_audioPath != null) {&#xA;      final Directory appDir = await getApplicationDocumentsDirectory();&#xA;      final timestamp = DateTime.now().millisecondsSinceEpoch;&#xA;      final String audioFileName = &#x27;audio_$timestamp.wav&#x27;;&#xA;      await File(_audioPath!).copy(&#x27;${appDir.path}/$audioFileName&#x27;);&#xA;      _showSuccess(&#x27;Saved: $audioFileName&#x27;);&#xA;    }&#xA;  }&#xA;&#xA;  Future<void> _stopAndSaveVideoRecording() async {&#xA;    try {&#xA;      final video = await _cameraController!.stopVideoRecording();&#xA;      _videoPath = video.path;&#xA;&#xA;      if (_videoPath != null) {&#xA;        final Directory appDir = await getApplicationDocumentsDirectory();&#xA;        final timestamp = DateTime.now().millisecondsSinceEpoch;&#xA;        final String videoFileName = &#x27;video_$timestamp.mp4&#x27;;&#xA;        final savedVideoPath = &#x27;${appDir.path}/$videoFileName&#x27;;&#xA;        await File(_videoPath!).copy(savedVideoPath);&#xA;&#xA;        setState(() {&#xA;          _recordedVideos.add(savedVideoPath);&#xA;          _isRecording = false;&#xA;        });&#xA;&#xA;        _showSuccess(&#x27;Saved: $videoFileName&#x27;);&#xA;      }&#xA;    } catch (e) {&#xA;      _showError(&#x27;Recording stop error: $e&#x27;);&#xA;    }&#xA;  }&#xA;&#xA;  Future<void> _switchCamera() async {&#xA;    if (_cameras.length &lt;= 1) return;&#xA;&#xA;    if (_isRecording) {&#xA;      await _stopAndSaveVideoRecording();&#xA;      _currentCameraIndex = (_currentCameraIndex &#x2B; 1) % _cameras.length;&#xA;      await _initializeCameraController(_cameras[_currentCameraIndex]);&#xA;      await _startVideoRecording();&#xA;    } else {&#xA;      _currentCameraIndex = (_currentCameraIndex &#x2B; 1) % _cameras.length;&#xA;      await _initializeCameraController(_cameras[_currentCameraIndex]);&#xA;    }&#xA;  }&#xA;&#xA;  Future<void> _toggleRecording() async {&#xA;    if (_cameraController == null) return;&#xA;&#xA;    if (_isRecording) {&#xA;      await _stopAndSaveVideoRecording();&#xA;      await _stopAndSaveAudioRecording();&#xA;    } else {&#xA;      _startVideoRecording();&#xA;      _startAudioRecording();&#xA;      setState(() => _recordedVideos.clear());&#xA;    }&#xA;  }&#xA;}&#xA;</void></void></void></void></void></void></void></void></void></string></cameradescription></mergevideorecording></mergevideorecording>

    &#xA;

  • FFmpeg RTSP drop rate increases when frame rate is reduced

    13 avril 2024, par Avishka Perera

    I need to read an RTSP stream, process the images individually in Python, and then write the images back to an RTSP stream. As the RTSP server, I am using Mediamtx [1]. For streaming, I am using FFmpeg [2].

    &#xA;

    I have the following code that works perfectly fine. For simplification purposes, I am streaming three generated images.

    &#xA;

    import time&#xA;import numpy as np&#xA;import subprocess&#xA;&#xA;width, height = 640, 480&#xA;fps = 25&#xA;rtsp_server_address = f"rtsp://localhost:8554/mystream"&#xA;&#xA;ffmpeg_cmd = [&#xA;    "ffmpeg",&#xA;    "-re",&#xA;    "-f",&#xA;    "rawvideo",&#xA;    "-pix_fmt",&#xA;    "rgb24",&#xA;    "-s",&#xA;    f"{width}x{height}",&#xA;    "-i",&#xA;    "-",&#xA;    "-r",&#xA;    str(fps),&#xA;    "-avoid_negative_ts",&#xA;    "make_zero",&#xA;    "-vcodec",&#xA;    "libx264",&#xA;    "-threads",&#xA;    "4",&#xA;    "-f",&#xA;    "rtsp",&#xA;    rtsp_server_address,&#xA;]&#xA;colors = np.array(&#xA;    [&#xA;        [255, 0, 0],&#xA;        [0, 255, 0],&#xA;        [0, 0, 255],&#xA;    ]&#xA;).reshape(3, 1, 1, 3)&#xA;images = (np.ones((3, width, height, 3)) * colors).astype(np.uint8)&#xA;&#xA;if __name__ == "__main__":&#xA;&#xA;    process = subprocess.Popen(ffmpeg_cmd, stdin=subprocess.PIPE)&#xA;    start = time.time()&#xA;    exported = 0&#xA;    while True:&#xA;        exported &#x2B;= 1&#xA;        next_time = start &#x2B; exported / fps&#xA;        now = time.time()&#xA;        if next_time > now:&#xA;            sleep_dur = next_time - now&#xA;            time.sleep(sleep_dur)&#xA;&#xA;        image = images[exported % 3]&#xA;        image_bytes = image.tobytes()&#xA;&#xA;        process.stdin.write(image_bytes)&#xA;        process.stdin.flush()&#xA;&#xA;    process.stdin.close()&#xA;    process.wait()&#xA;

    &#xA;

    The issue is, that I need to run this at 10 fps because the processing step is heavy and can only afford 10 fps. Hence, as I reduce the frame rate from 25 to 10, the drop rate increases from 0% to 100%. And after a few iterations, I get a BrokenPipeError: [Errno 32] Broken pipe. Refer to the appendix for the complete log.

    &#xA;

    As an alternative, I can use OpenCV compiled from source with GStreamer [3], but I prefer using FFmpeg to make the shipping process simple. Since compiling OpenCV from source can be tedious and dependent on the system.

    &#xA;

    References

    &#xA;

    [1] Mediamtx (formerly rtsp-simple-server) : https://github.com/bluenviron/mediamtx

    &#xA;

    [2] FFmpeg : https://github.com/FFmpeg/FFmpeg

    &#xA;

    [3] Compile OpenCV with GStreamer : https://github.com/bluenviron/mediamtx?tab=readme-ov-file#opencv

    &#xA;

    Appendix

    &#xA;

    Creating the source stream

    &#xA;

    To instantiate the unprocessed stream, I use the following command. This streams the content of my webcam as and RTSP stream.

    &#xA;

    ffmpeg -video_size 1280x720 -i /dev/video0  -avoid_negative_ts make_zero -vcodec libx264 -r 10 -f rtsp rtsp://localhost:8554/webcam&#xA;

    &#xA;

    Error log

    &#xA;

    ffmpeg version 6.1.1 Copyright (c) 2000-2023 the FFmpeg developers&#xA;  built with gcc 12.3.0 (conda-forge gcc 12.3.0-5)&#xA;  configuration: --prefix=/home/conda/feedstock_root/build_artifacts/ffmpeg_1712656518955/_h_env_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_plac --cc=/home/conda/feedstock_root/build_artifacts/ffmpeg_1712656518955/_build_env/bin/x86_64-conda-linux-gnu-cc --cxx=/home/conda/feedstock_root/build_artifacts/ffmpeg_1712656518955/_build_env/bin/x86_64-conda-linux-gnu-c&#x2B;&#x2B; --nm=/home/conda/feedstock_root/build_artifacts/ffmpeg_1712656518955/_build_env/bin/x86_64-conda-linux-gnu-nm --ar=/home/conda/feedstock_root/build_artifacts/ffmpeg_1712656518955/_build_env/bin/x86_64-conda-linux-gnu-ar --disable-doc --disable-openssl --enable-demuxer=dash --enable-hardcoded-tables --enable-libfreetype --enable-libharfbuzz --enable-libfontconfig --enable-libopenh264 --enable-libdav1d --enable-gnutls --enable-libmp3lame --enable-libvpx --enable-libass --enable-pthreads --enable-vaapi --enable-libopenvino --enable-gpl --enable-libx264 --enable-libx265 --enable-libaom --enable-libsvtav1 --enable-libxml2 --enable-pic --enable-shared --disable-static --enable-version3 --enable-zlib --enable-libopus --pkg-config=/home/conda/feedstock_root/build_artifacts/ffmpeg_1712656518955/_build_env/bin/pkg-config&#xA;  libavutil      58. 29.100 / 58. 29.100&#xA;  libavcodec     60. 31.102 / 60. 31.102&#xA;  libavformat    60. 16.100 / 60. 16.100&#xA;  libavdevice    60.  3.100 / 60.  3.100&#xA;  libavfilter     9. 12.100 /  9. 12.100&#xA;  libswscale      7.  5.100 /  7.  5.100&#xA;  libswresample   4. 12.100 /  4. 12.100&#xA;  libpostproc    57.  3.100 / 57.  3.100&#xA;Input #0, rawvideo, from &#x27;fd:&#x27;:&#xA;  Duration: N/A, start: 0.000000, bitrate: 184320 kb/s&#xA;  Stream #0:0: Video: rawvideo (RGB[24] / 0x18424752), rgb24, 640x480, 184320 kb/s, 25 tbr, 25 tbn&#xA;Stream mapping:&#xA;  Stream #0:0 -> #0:0 (rawvideo (native) -> h264 (libx264))&#xA;[libx264 @ 0x5e2ef8b01340] using cpu capabilities: MMX2 SSE2Fast SSSE3 SSE4.2 AVX FMA3 BMI2 AVX2&#xA;[libx264 @ 0x5e2ef8b01340] profile High 4:4:4 Predictive, level 2.2, 4:4:4, 8-bit&#xA;[libx264 @ 0x5e2ef8b01340] 264 - core 164 r3095 baee400 - H.264/MPEG-4 AVC codec - Copyleft 2003-2022 - http://www.videolan.org/x264.html - options: cabac=1 ref=3 deblock=1:0:0 analyse=0x3:0x113 me=hex subme=7 psy=1 psy_rd=1.00:0.00 mixed_ref=1 me_range=16 chroma_me=1 trellis=1 8x8dct=1 cqm=0 deadzone=21,11 fast_pskip=1 chroma_qp_offset=4 threads=4 lookahead_threads=1 sliced_threads=0 nr=0 decimate=1 interlaced=0 bluray_compat=0 constrained_intra=0 bframes=3 b_pyramid=2 b_adapt=1 b_bias=0 direct=1 weightb=1 open_gop=0 weightp=2 keyint=250 keyint_min=10 scenecut=40 intra_refresh=0 rc_lookahead=40 rc=crf mbtree=1 crf=23.0 qcomp=0.60 qpmin=0 qpmax=69 qpstep=4 ip_ratio=1.40 aq=1:1.00&#xA;Output #0, rtsp, to &#x27;rtsp://localhost:8554/mystream&#x27;:&#xA;  Metadata:&#xA;    encoder         : Lavf60.16.100&#xA;  Stream #0:0: Video: h264, yuv444p(tv, progressive), 640x480, q=2-31, 10 fps, 90k tbn&#xA;    Metadata:&#xA;      encoder         : Lavc60.31.102 libx264&#xA;    Side data:&#xA;      cpb: bitrate max/min/avg: 0/0/0 buffer size: 0 vbv_delay: N/A&#xA;[vost#0:0/libx264 @ 0x5e2ef8b01080] Error submitting a packet to the muxer: Broken pipe   &#xA;[out#0/rtsp @ 0x5e2ef8afd780] Error muxing a packet&#xA;[out#0/rtsp @ 0x5e2ef8afd780] video:1kB audio:0kB subtitle:0kB other streams:0kB global headers:0kB muxing overhead: unknown&#xA;frame=    1 fps=0.1 q=-1.0 Lsize=N/A time=00:00:04.70 bitrate=N/A dup=0 drop=70 speed=0.389x    &#xA;[libx264 @ 0x5e2ef8b01340] frame I:16    Avg QP: 6.00  size:   147&#xA;[libx264 @ 0x5e2ef8b01340] frame P:17    Avg QP: 9.94  size:   101&#xA;[libx264 @ 0x5e2ef8b01340] frame B:17    Avg QP: 9.94  size:    64&#xA;[libx264 @ 0x5e2ef8b01340] consecutive B-frames: 50.0%  0.0% 42.0%  8.0%&#xA;[libx264 @ 0x5e2ef8b01340] mb I  I16..4: 81.3% 18.7%  0.0%&#xA;[libx264 @ 0x5e2ef8b01340] mb P  I16..4: 52.9%  0.0%  0.0%  P16..4:  0.0%  0.0%  0.0%  0.0%  0.0%    skip:47.1%&#xA;[libx264 @ 0x5e2ef8b01340] mb B  I16..4:  0.0%  5.9%  0.0%  B16..8:  0.1%  0.0%  0.0%  direct: 0.0%  skip:94.0%  L0:56.2% L1:43.8% BI: 0.0%&#xA;[libx264 @ 0x5e2ef8b01340] 8x8 transform intra:15.4% inter:100.0%&#xA;[libx264 @ 0x5e2ef8b01340] coded y,u,v intra: 0.0% 0.0% 0.0% inter: 0.0% 0.0% 0.0%&#xA;[libx264 @ 0x5e2ef8b01340] i16 v,h,dc,p: 97%  0%  3%  0%&#xA;[libx264 @ 0x5e2ef8b01340] i8 v,h,dc,ddl,ddr,vr,hd,vl,hu:  0%  0% 100%  0%  0%  0%  0%  0%  0%&#xA;[libx264 @ 0x5e2ef8b01340] Weighted P-Frames: Y:52.9% UV:52.9%&#xA;[libx264 @ 0x5e2ef8b01340] ref P L0: 88.9%  0.0%  0.0% 11.1%&#xA;[libx264 @ 0x5e2ef8b01340] kb/s:8.27&#xA;Conversion failed!&#xA;Traceback (most recent call last):&#xA;  File "/home/avishka/projects/read-process-stream/minimal-ffmpeg-error.py", line 58, in <module>&#xA;    process.stdin.write(image_bytes)&#xA;BrokenPipeError: [Errno 32] Broken pipe&#xA;</module>

    &#xA;